Is a function generating styled-components keyframes expensive? - javascript

while working with styled-components i was trying to create a function with some inputs returning a css tagged function which needed some specific keyframes (tightly coupled with my css tag function).
I ended up defining my keyframes inside the function to have access to the closure and was asking my self if the fact to redefine each time my keyframes for each call of the function could be expensive in terms of performance, and if there was a better way to handle this case.
Here is a snippet which illustrate what I try to explain, of course my question make only sense for a much bigger and complex keyframe than this one :
const growBorder = (color, from, to) => {
const grow = keyframes`
from { border: ${from} solid ${color}; }
to { border: ${to} solid ${color}; }
`
return css`
border: ${from} solid ${color};
animate: ${grow} 3s linear 1s infinite alternate;
`
}
const Button = styled.button`
${growBorder('purple', '1px', '3px')}
`
const UglyButton = styled.button`
${growBorder('red', '10px', '30px')}
`
...

The answer is yes. Creating keyframes like yours is expensive.
If you look at the definition of keyframes inside styled-components, you can see that every time styled-components sees a keyframes helper, it hashes the content of the keyframes declaration. If no match is found (as would be the case for dynamic keyframes like yours), it will inject a new style rule into the document's style sheet, forcing a global reflow.

Related

How do I get a CSS transition to apply to styles dynamically generated by React?

I have a button component that makes use of a theme provided by a context:
Button.js
() => {
const theme = useContext(themeContext); // { primaryColor: "blue" }
return <button className="MyButton" styles={{ backgroundColor: theme.primaryColor }}>Hello</button>
}
Button.styles.scss
.MyButton {
background-color: purple
transition: background-color 0.1s
&:hover {
background-color: green
}
}
Since the background color is now being set by React, the transition effect no longer works. Is there a way to get this to work without, for example, rewriting everything in JSS (given that all the app's styling is done in scss).
Did you verify that the React theme isn't setting transition and hover rules? If it is, then the only way to override those is to set the styles inline. Perhaps add onto the theme object (or a clone of it) before assigning in the styles attr, or somehow overlay the theme nearer to its source.

How is it possible to transition the position of items in a list without using transform or top/left

The other day I stumbled onto an example that uses Vue.js, but my question is more about the CSS and HTML that Vue uses to achieve the transition between states.
The cards temporarily get the class .shuffleMedium-move which adds a transition: transform 1s and the order of the nodes change in the DOM, but I don't understand why the transition occurs since the transform property never seems to get set and the items are positioned simply using float:left.
I've been doing CSS for quite a while and I've always had to resort to using a combination of JavaScript position: absolute and transform to achieve a similar result. Vue's solution seems really elegant, but I don't understand how it works.
From the documentation on list transition
This might seem like magic, but under the hood, Vue is using an animation technique called FLIP to smoothly transition elements from their old position to their new position using transforms.
From the FLIP article
FLIP stands for First, Last, Invert, Play.
Let’s break it down:
First: the initial state of the element(s) involved in the transition.
Last: the final state of the element(s).
Invert: here’s the fun bit. You figure out from the first and last how the element has changed, so – say – its width, height,
opacity. Next you apply transforms and opacity changes to reverse, or
invert, them. If the element has moved 90px down between First and
Last, you would apply a transform of -90px in Y. This makes the
elements appear as though they’re still in the First position but,
crucially, they’re not.
Play: switch on transitions for any of the properties you changed, and then remove the inversion changes. Because the element or
elements are in their final position removing the transforms and
opacities will ease them from their faux First position, out to the
Last position.
Step by step example
That way, we can inspect changes at each step of the animation process.
When it's playing in real time, the transform is really quickly added inline and it's then removed immediately, so it looks like it's never set.
const el = document.getElementById('target');
const data = {};
function first() {
data.first = el.getBoundingClientRect();
console.log('First: get initial position', data.first.left, 'px');
}
function last() {
el.classList.toggle('last');
data.last = el.getBoundingClientRect();
console.log('Last: get new position', data.last.left, 'px');
}
function invert() {
el.style.transform = `translateX(${data.first.left - data.last.left}px)`;
console.log('Invert: applies a transform to place the item where it was.');
}
function play() {
requestAnimationFrame(() => {
el.classList.add('animate');
el.style.transform = '';
});
console.log('Play: adds the transition class and removes the transform.');
}
function end() {
el.classList.remove('animate');
console.log('End: removes the transition class.');
}
const steps = [first, last, invert, play, end];
let step = 0;
function nextStep() {
steps[step++ % steps.length]();
}
document.getElementById('next').addEventListener('click', nextStep);
.last {
margin-left: 35px;
}
.animate {
transition: transform 1s;
}
#target {
display: inline-block;
padding: 5px;
border: 1px solid #aaa;
background-color: #6c6;
}
#next {
margin-top: 5px;
}
<div id="target">target</div>
<br>
<button id="next" type="button">Next</button>

Wait for class to be added before modifying styles

I'm trying to do something like this:
element.classList.add('animate-in')
element.style.transform = 'translateY(100px)';
Where the animate-in class has a transition: transform 1s property for animation. But when I run this it the element is translated 100px down but without the transition, seems like the transform property is modified before the class name can be added and take affect. I don't want to use setTimeout since it can be unreliable in animations. Is there any way to wait for the class to be added before modifying any styles?
I don't think we get any events for the 'application of styles' through JavaScript, which is pretty frustrating in this case...
I found this thread that's pretty helpful.
One thing to note: the animation will work even if you use setTimeout with 0 ms. What was the reason in particular that it messed with animations?
setTimeout(function(){
element.style.transform = 'translateY(100px)'
}, 0)
Also, if you set it up with an event, you can change the style directly and see the animation applied.
Here's the bin I was playing around with: https://jsbin.com/lofotutumu/edit?html,css,js,output
You don't really want to wait, but want the properties embedded with this new class to be applied.
And this can be done, synchronously, by triggering a reflow.
Some properties/functions need the DOM boxes to be recalculated in order to return their values. To do so, the browser has to trigger a reflow on the page, and hence will apply your new properties.
But note that this has a performance impact, particularly on complex pages, so use it with care.
element.classList.add('animate-in');
element.offsetTop; // trigger a reflow
element.style.transform = 'translateY(100px)';
.animate-in {
transition: transform 2s linear;
}
<div id="element">Hello</div>
Would creating a keyframe and adding that into your class suit your needs? E.g.
// This is your keyframe to animate your element
#keyframes translateDown {
0%{
transform:translateY(0);
}
100%{
transform:translateY(100px);
}
}
.animate-in{
animation: 2s linear translateDown both;
}

Loop colour transition with CSS

I would like to be able to loop a colour transition for the background colour of my website, so it slowly shifts between two subtly different hues as the users browse, to give variation to their browsing experience. I'm wanting something like the following:
.background {
background-color: #00A0D6;
transition: background-color: 100s;
}
But with background-color cycling back to #00A0D6, then beginning the transition back to #00B1D7 as soon as it has reached #00A0D6. I would also like this to happen automatically, from the moment a user browses to the site.
If possible I would like to do this in vanilla CSS. If not, is there a way to use a Javascript loop to cycle the value in this way? Possibly with the jQuery library? I would like the most processor-efficient method possible which still retains a smooth transition, since this is for an online RPG, which will be doing a lot of other transitions and databasing at the same time.
You can use CSS #keyframes to achieve what you want:
#keyframes changeColor {
from {
background-color: #00A0D6;
}
to {
background-color: #00B1D7;
}
}
Then you use it with the animation property in the desired elements:
html, body {
animation: changeColor 100s infinite alternate;
}
More on CSS Keyframes here.

Using addClass() and CSS transition on render function in Backbone app not working correctly

In my backbone.js application, I'm trying to fade in the view element after it's been appended. However it doesn't work.
Live example here: http://metropolis.pagodabox.com
var itemRender = view.render().el;
$('#items-list').append(itemRender);
$(itemRender).addClass('show');
However if I add a small setTimeout function, it works.
var itemRender = view.render().el;
$('#items-list').append(itemRender);
setTimeout(function(){
$(itemRender).addClass('show');
},10);
Using fadeIn() also works but I prefer to use straight CSS for the transition as it's more efficient, and prefer not to use any setTimeout "hacks" to force it to work. Is there a callback I can use for append? Or any suggestions? The full code is below:
itemRender: function (item) {
var view = new app.ItemView({ model: item }),
itemName = item.get('name'),
itemRender = view.render().el;
$('#items-list').append(itemRender);
$(itemRender).addClass('show');
app.itemExists(itemName);
}
CSS/LESS:
#items-list li {
padding: 0 10px;
margin: 0 10px 10px;
border: 1px solid #black;
.border-radius(10px);
position: relative;
.opacity(0);
.transition(opacity)
}
#items-list li.show {.opacity(1)}
This "hack" you mention (or some variant of it) is occasionally necessary for web development, simply due to the nature of how browsers render pages.
(NOTE: This is all from memory, so while the overall idea is right please take any details with a small grain of salt.)
Let's say you do the following:
$('#someElement').css('backgroundColor', 'red');
$('#someElement').css('backgroundColor', 'blue');
You might expect to see the background color of #someElement flash red for a brief moment, then turn blue right? However, that won't happen, because browsers try to optimize rendering performance by only rendering the final state at the end of the JS execution. As a result, the red background will never even appear on the page; all you'll ever see is the blue.
Similarly here, the difference between:
append
set class
and:
append
wait 1ms for the JS execution to finish
set class
Is that the latter allows the element to enter the page and AFTER the JS is executed have its style change, while the former just applies the style change before the element gets shown.
So while in general window.setTimeout should be avoided, when you need to deal with these ... complications of browser rendeering, it's really the only way to go. Personally I like using the Underscore library's defer function:
var itemRender = view.render().el;
$('#items-list').append(itemRender);
_(function(){
$(itemRender).addClass('show');
}).defer();
It's the same darn thing, but because it's encapsulated in a library function it feels less dirty to me :-) (and if the "post-render" logic is more than a line or two I can factor it in to a Backbone View method and do _(this.postRender).defer() inside my render method).
You can use CSS animations
#keyframes show {
0% { opacity: 0; }
100% { opacity: 1; }
}
#items-list li {
padding: 0 10px;
margin: 0 10px 10px;
border: 1px solid #black;
.border-radius(10px);
position: relative;
}
#items-list li.show {
animation: show 1s;
}

Categories