Changing CSS #keyframe value with Javascript [duplicate] - javascript

This question already has an answer here:
How to change #keyframes vlaues using javascript?
(1 answer)
Closed 1 year ago.
I have the following CSS Keyframe:
#keyframes progressStatus
{
to
{
stroke-dashoffset: 165;
}
}
I am trying to change the value 165 to something else with Javascript.

For this particular example you could use a CSS variable.
This simple snippet alters a variable called --strokeDashoffset every time the div is clicked (and toggles between having an animation and not, just for the demo).
div {
--strokeDashoffset: 165px; /* initial position */
background-color: magenta;
width: var(--strokeDashoffset);
height: 5vmin;
animation: none;
animation-fill-mode: forwards;
animation-duration: 1s;
animation-iteration-count: 1;
}
.strokeDashoffset {
animation-name: progressStatus;
}
#keyframes progressStatus
{
to
{
stroke-dashoffset: var(--strokeDashoffset);
}
}
<div onclick="this.classList.toggle('strokeDashoffset'); this.style.setProperty('--strokeDashoffset', Math.random(0,1)*100 + 'vmin');" >CLICK ME</div>

While the CSS variable approach in the other answer is nicer, here is one that modifies the relevant CSS directly since I already typed it out. This might be useful if you can't rely on CSS custom properties (variables) or a polyfill.
In the CSS Object Model, a #keyframes rule has its own array of child rules corresponding to the keyframes themselves (from, to and/or percentages). So, if your example were the first stylesheet in the document (and had no other rules):
const stylesheet = document.stylesheets[0];
const progressStatus = stylesheet.cssRules[0];
const toKeyframe = progressStatus.cssRules[0];
toKeyframe.style.strokeDashoffset = '170 80'; // or whatever desired value
Picking stylesheets and nested rules by array indexes is cumbersome, error-prone and easily breaks with changes. In production code, you'll at least want to locate the rule by iterating through all rules with various tests, like rule.type === CSSRule.KEYFRAMES_RULE && rule.name === 'progressStatus'.

Related

My css animation is consuming a lot of resources

I'm trying to make a starring night with twinkling stars in css3 + Javascript, however, my animation is consuming a lot of CPU, the main animation:
#for $i from 0 through 400 {
.star:nth-child(#{$i}) {
$star-size: (random() * (1-4) +4) + px;
top: (random(100)) + vh;
left: (random(100)) + vw;
width: $star-size;
height: $star-size;
animation: blinker 1.2s alternate infinite ease-in-out;
animation-delay: (random(30) / 10) + s;
transform: scale(0.2);
}
}
#keyframes blinker {
100% {
transform: scale(1);
}
}
the full code: https://jsfiddle.net/sam7krx0/
is there any way to make this code perform better?
Edit:
tried with translateZ(0) and with will-change: transform but the animation still being rendered by the CPU.
https://jsfiddle.net/8hn97kcx/2/
Edit 2:
It seems that firefox might be the problem, while testing on chrome the animation uses way less CPU.
Edit 3:
profile of the fiddle above running on firefox developer edition 69.0b4:
firefox profile
CPU usage:
Have you tried using the will-change property - this helps the browser know about the change and offload it to the compositor if possible.
The OP code was horrendously inefficient in that it uses 400+ uniquely generated selectors. So the bulk of the processing time involves maintaining the CSS animation loop and looking up 400+ classes on each alternation of said CSS animation. This is a rare case wherein class selectors are a burden and not useful. Since each s.star needs these unique styles, it would take less computing power to generate the CSS property values on a template literal and then assign it to the tag as an inline-style. (See Demo)
Besides doing away with ridiculously huge .class lists on a bloated stylesheet, the demo makes full use of a documentFragment. DOM operations are expensive on resources (imagine 400+ tags being appended to one location). Doing everything on the fragment, then finally to the DOM by 👍appending documentFragment just once and 400 .star are in the DOM👍. The OP code on the other hand 👎will append 400 s.star one at a time... that's 400+ DOM operations.👎
Also on the OP code it is deceiving as to the size of the actual CSS. SCSS, a post-processor is used, so what looks like 8 lines of weird looking CSS is actually 👎3200 lines of CSS👎 after it has been compiled and cached by the browser. The CSS in the demo is what it appears to be ...👍9 lines👍 for .star selector.
/**| documentFragment
- The only global variable points to a documentFragment not attached to the DOM.
- Use fragment as you would a document object when moving, creating, destroying,
appending, detaching, etc... HTML element tags from and to the DOM. Doing so will
greatly improve processing times when adding 400+ uniquely styled tags.
- When all .star tags have been created, modified, and appended to the fragment --
only the fragment itself needs to be appended to the DOM instead of 400 tags.
*/
var fragment = document.createDocumentFragment();
/**| randomRange(min, max, integer = false)
#Params: min [number].....: The minimum
max [number].....: The maximum
integer [boolean]: default is false which results will be floats.
If true then results will be integers.
Utility function that will return a random number from a given range of consecutive
numbers.
*/
const randomRange = (min, max, integer = false) => {
let numbers = integer ? {
min: Math.ceil(min),
max: Math.floor(max)
} : {
min: min,
max: max
};
return Math.random() * (numbers.max - numbers.min + 1) + numbers.min;
};
/**| starGenerator(limit)
#Params: limit [number]: The number of s.star to generate.
A generator function that creates s.star tags. Assigning individual tag properties
and setting randomly determined values would involve a ton of unique selectors.
To avoid a ton of lookups in a CSS stylesheet a mile long, it's easier to create and
maintain one template literal of the CSS properties interpolated with random values.
Each s.star would be assigned an inline-style of five CSS properties/values by one
statement via `.cssText` property.
*/
function* starGenerator(limit) {
let iteration = 0;
while (iteration < limit) {
iteration++;
const star = document.createElement("s");
star.classList.add("star");
let properties = `
width: ${randomRange(1, 4)}px;
height: ${randomRange(1, 4)}px;
top: ${randomRange(0, 100, true)}vh;
left: ${randomRange(0, 100, true)}vw;
animation-delay: ${randomRange(1, 30, true) / 10}s`;
star.style.cssText = properties;
yield star;
}
return fragment;
}
/**| nightfall(selector, limit = 400)
#Params: selector [string]: Target parent tag
limit [number].. : The maximum number of s.star to generate.
Interface function that facilitates DOM procedures with minimal presence in DOM.
*/
const nightfall = (selector, limit = 400) => {
const base = document.querySelector(selector);
base.classList.add('sky');
for (let star of starGenerator(limit)) {
fragment.appendChild(star);
}
return base.appendChild(fragment);
};
// Call nightfall() passing the selector "main"
nightfall("main");
.sky {
position: relative;
background: #000;
height: 100vh;
overflow: hidden;
}
.star {
display: block;
position: absolute;
animation: twinkle 1.2s alternate infinite ease-in-out;
transform: scale(0.2);
border-radius: 50%;
background: #fff;
box-shadow: 0 0 6px 1px #fff;
z-index: 2;
text-decoration: none;
}
#keyframes twinkle {
100% {
transform: scale(1);
}
}
<main></main>
That's because the rendering is done by CPU which can be a loose in performance. There is an option in CSS to run such an animation on GPU.
Your snippet adjusted
#for $i from 0 through 400 {
.star:nth-child(#{$i}) {
$star-size: (random() * (1-4) +4) + px;
transform: translateY((random(100)) + vh) translateX((random(100)) + vw) translateZ(0);
width: $star-size;
height: $star-size;
animation: blinker 1.2s alternate infinite ease-in-out;
animation-delay: (random(30) / 10) + s;
transform: scale(0.2);
}
}
#keyframes blinker {
100% {
transform: scale(1);
}
}
It's very important to add translateZ because only 3D renderings are done by GPU.
Doing animations on GPU is also called accelerated animations, please check this helpful article for more information about: https://www.sitepoint.com/introduction-to-hardware-acceleration-css-animations/
it's not only problem with you code.
it's also from your CPU ability, trying to upgrade your CPU and RAM to perform better.
sometimes you can't build mid - high animation in low spec computer.

How to trigger a CSS animation?

I am stuck trying to trigger a CSS animation.
My CSS:
h3[id="balance-desktop"]{
color: black;
-webkit-animation-name: gogreen;
-webkit-animation-duration: 2s;
animation-name: gogreen;
animation-duration: 2s
}
/* Safari 4.0 - 8.0 */
#-webkit-keyframes gogreen {
from {color: black;}
to {color: limegreen;}
from{color: limegreen;}
to{color: black;}
}
/* Standard syntax */
#keyframes gogreen {
from {color: black;}
to {color: limegreen;}
from{color: limegreen;}
to{color: black;}
}
It is a basic animation that changes color of a h3[id=balance-desktop] element.
The animation works when the page loads. I am trying to get it to work when I call my java script function but I am unable too.
Attempt #1:
function showGreenAmiamtion(){
var test = document.getElementById("balance-desktop");
test.style.webkitAnimationName = 'gogreen';
test.style.webkitAnimationDuration = '4s';
}
Attempt #2:
function showGreenAmiamtion(){
document.getElementById("balance-desktop").style.webkitAnimationName = "";
setTimeout(function ()
{
document.getElementById("balance-desktop").style.webkitAnimationName = "gogreen";
}, 0);
}
I tried all answers from How to activate a CSS3 (webkit) animation using javascript?, no luck.
Is something wrong with my code?
Your attempt 2 works - just cleaned up your code a bit to remove the webkit prefixes (which are a few years outdated). I'm setting the animationName to 'none' inline, and then removing that so the element goes back to using its original CSS animation name.
Also, having multiple from and tos in a keyframe animation won't work, so I formatted it to work with percentages.
var btn = document.getElementById("button");
var text = document.getElementById("balance-desktop");
function turngreen() {
text.style.animationName = 'none';
setTimeout(function() {
text.style.animationName = null;
}, 0);
}
btn.addEventListener('click', turngreen);
#balance-desktop {
color: black;
animation: gogreen 2s;
}
#keyframes gogreen {
0%, 100% { color: black; }
50% { color: limegreen; }
}
<h3 id="balance-desktop">Heading</h3>
<button id="button">Turn Green</button>
For something like this, a CSS Transition might be more simple.
Also, all major browsers support CSS3 Animations and Transitions these days, so unless you are targeting old browsers, you can drop the vendor prefixes.
// Get DOM references:
var btn = document.querySelector("button");
var h3 = document.querySelector(".balance-desktop");
// Set up trigger for transition function. This is a button
// click in this example, but the function can be called anytime
// you like.
btn.addEventListener("click", function(){
// By changing a style property on the element that had previously
// been set, and if that element has been set up for transitions
// on that property, the transition will be activated.
h3.classList.add("goGreen");
// After the transition to the new style is complete
// we'll remove that style, effectively causing a
// transition back to the original style. It's important
// that the delay is set to at least the time of the transition.
// Two seconds in this case.
setTimeout(function(){
h3.classList.remove("goGreen");
},2000);
});
.balance-desktop{
color: black;
/* Set the element to transition on all properties
that have been set over the course of 2 seconds. */
transition:2s all;
}
.goGreen {
color: limegreen;
}
<h3 class="balance-desktop">Hello</h3>
<button>Run Function</button>

Updating animation-duration in Javascript

After changing the animation-duration (or in this case, -webkit-animation-duration) property via JavaScript with setProperty("-webkit-animation-duration", value + "s"), I see the change in the element inspector in Chrome, but the actual animation speed doesn't change. In addition, if I manually change the value in the element inspector, there is no change either.
I've got an input field set up to take an animation speed value, which is connected to the following event listener (orbitFactor is a global var defined elsewhere):
function updateSpeed(event) {
var planetDiv = document.getElementById(event.target.id);
planetDiv.style.setProperty("width", event.target.value / orbitFactor);
planetDiv.style.setProperty("height", event.target.value / orbitFactor);
planetDiv.style.setProperty("-webkit-animation-duration", event.target.value + "s");
}
The event listener is definitely getting called, and the -webkit-animation-duration value does change in the element inspector, but the speed of the animation doesn't. Is there something I'm missing here with regards to -webkit-animation-duration? The other properties I'm changing (e.g. width and height) using the same method do change visibly.
Thanks in advance
EDIT: Note that this is a problem in Chrome 40, but it works properly in Chrome 42 and Firefox 35.
Setting the style element directly using the [] to access either the vendor-prefixed or native css prop. will allow you to re-apply the animation duration property and change the rotational speed of the planet. No jquery needed. It's also worth mentioning that at the time of writing Firefox supports a non-prefixed version of the css property, while there is either mixed support or vendor-prefix support for other browsers. If considering using these animations, a given developer should seriously consider their potential user-base and probably not make this a core feature of web app. See more support info here:
http://caniuse.com/#feat=css-animation
Le code:
orbitFactor = 1e6
function updateSpeed(event) {
var orbitDiv = document.getElementById("Mercury-orbit");
orbitDiv.style["-webkit-animation-duration"] = event.target.value + "s";
}
function updateDiameter(event) {
var planetDiv = document.getElementById("Mercury");
planetDiv.style["width"] = event.target.value + "px";
planetDiv.style["height"] = event.target.value + "px";
}
document.getElementById("orbit-period").addEventListener("change", updateSpeed);
document.getElementById("planet-diameter").addEventListener("change", updateDiameter);
It's not easy to restart CSS animation or change its parameter. However, I found some trick. See the following code. I separated the animation parameters into the class, which I add / remove. Plus the trick found in CSS-Tricks article and it works:
$(document).ready(function() {
$('#slow-btn').click(function(){
$('#testdiv').removeClass("testanimation");
$('#testdiv').css("-webkit-animation-duration", "5s");
$('#testdiv').get(0).offsetWidth = $('#testdiv').get(0).offsetWidth;
$('#testdiv').addClass("testanimation");
});
$('#fast-btn').click(function(){
$('#testdiv').removeClass("testanimation");
$('#testdiv').css("-webkit-animation-duration", "1s");
$('#testdiv').get(0).offsetWidth = $('#testdiv').get(0).offsetWidth;
$('#testdiv').addClass("testanimation");
});
});
#testdiv {
display: block;
position: absolute;
left: 100px;
width: 100px;
height: 100px;
background: red;
}
.testanimation {
-webkit-animation: myanimation 2s linear alternate infinite;
animation: myanimation 2s linear alternate infinite;
}
#-webkit-keyframes myanimation {
from {left: 100px;}
to {left: 400px;}
}
#keyframes myanimation {
from {left: 100px;}
to {left: 400px;}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='testdiv' class='testanimation'></div>
<input id='slow-btn' type='button' value='slow' />
<input id='fast-btn' type='button' value='fast' />
in w3c standard, it says that it doesn't mention we can change the animation duration time or not. so it all depends on explorer.chrome yes, but ie no. So, we should update the whole animation when we want to change the time.
var animation = 'animationName time linear infinite';
var $element= $('selector').css('animation', 'none');
setTimeout(function(){
$element.css('animation', animation);
});
this work on IE

what is ng-hide-add, ng-hide-active

I'm animating a div. It has the following definition:
<div ng-show="showTranslations" ng-swipe-right="showTranslationsBlock=false">...</div>
I have the following css defined:
div.ng-hide {
transition: 0.5s linear opacity;
opacity: 0;
}
div.ng-hide-add,
div.ng-hide-remove {
/* this needs to be here to make it visible during the animation
since the .ng-hide class is already on the element rendering
it as hidden. */
display:block!important;
}
This is taken from this tutorial. The animation works. But:
Why do I need these classes .ng-hide-add and .ng-hide-remove?
Why I don't see them added to div's classes?
Why there are also classes ng-hide-add-active and ng-hide-remove-active?
Why there is no transition when the div becomes visible although I've added the following css rule:
div.ng-hide-remove {
opacity: 1;
}
UPDATE
As I can see from the table provided by google's tutorial these classes are added to trigger animation frame (this performs a reflow). Is my understanding correct? Why is animation frame is mentioned there?
I tried to increase the transition period but it didn't add the classes. I didn't see the classes ng-hide-add-active and ng-hide-remove-active added either.
As I understand from the table these are the classes that trigger transition?
UPDATE1
I've explored the Angular's source code and found the following for the ng-hide directive:
var ngHideDirective = ['$animate', function($animate) {
return function(scope, element, attr) {
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
$animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide');
});
};
}];
As I understand the ng-hide class is added through animation service. But what happens if I don't use animations and $animate service is not available? How Angular is going to handle this situation given the code above and how it is going to add ng-hide class? Or is this $animate.addClass() simply adds a callback to addClass event?
Put your CSS transition on ng-hide-remove, ng-hide-remove-active:
div.ng-hide-remove {
transition: 0.5s linear opacity;
opacity: 0;
}
div.ng-hide-remove-active {
opacity: 1;
}
Similarly, for ng-hide-add and ng-hide-add-active:
div.ng-hide-add {
transition: 0.5s linear opacity;
opacity: 1;
}
div.ng-hide-add-active {
opacity: 0;
}

How can I start CSS3 Animations at a specific spot?

I'm using CSS3 Animations, and I want to be able to move to a specific spot in the animation. For instance, if the CSS looks like this (and pretend that I used all the proper prefixes):
#keyframes fade_in_out_anim {
0% { opacity: 0; }
25% { opacity: 1; }
75% { opacity: 1; }
100% { opacity: 0; }
}
#fade_in_out {
animation: fade_in_out_anim 5s;
}
then I would like to be able to stop the animation, and move it to the 50% mark. I guess that the ideal JavaScript would look something like this:
var style = document.getElementById('fade_in_out').style;
style.animationPlayState = 'paused';
// Here comes the made up part...
style.animation.moveTo('50%'); // Or alternately...
style.animationPlayPosition = '50%';
Does anyone know of a way to make this happen (hopefully in Webkit)?
We can use the animation-delay property. Usually it delays animation for some time, and, if you set animation-delay: 2s;, animation will start two seconds after you applied the animation to the element. But, you also can use it to force it to start playing animation with a specific time-shift by using a negative value:
.element-animation{
animation: animationFrames ease 4s;
animation-delay: -2s;
}
http://default-value.com/blog/2012/10/start-css3-animation-from-specified-time-frame/

Categories

Resources