How to iterate keyframe percentages Less CSS
It seems like the Less compiler does not evaluate functions when directly used as a selector. Solution would be to make use of a temporary variable like in either of the below snippets:
.loop (@n, @index: 0) when (@index <= @n) { /* note the <= as we need 100% frame also */
@keyframeSel: percentage(@index/@n); /* note the lack of * 100 as Less already does it */
@{keyframeSel}{
prop: value;
}
.loop(@n, (@index + 1)); // Next iteration.
}
@keyframes anim {
.loop(20); // Launch the loop.
}
or
.loop (@n, @index: 0) when (@index <= @n) {
@keyframeSel: @index/@n * 100%;
@{keyframeSel}{
prop: value;
}
.loop(@n, (@index + 1)); // Next iteration.
}
@keyframes anim {
.loop(20); // Launch the loop.
}
Expressing CSS3 @keyframes using seconds instead of percentages
Don't forget you can run multiple animations on the same element, and that you can set their duration
, delay
and all other animation-...
rules independently.
E.g, you can split all your keyframes to single-key @keyframes rules.
Then it's easy to control when they'll kick in and to chain them.
div {
width: 120px;
height: 120px;
background-color: violet;
animation-fill-mode: forwards;
animation-name: orange, yellow, green, cyan, blue, violet;
animation-delay: 0s, 3s, 6s, 9s, 12s, 15s, 18s;
animation-duration: 3s; /* same for all */
}
@keyframes orange {
to { background-color: orange; }
}
@keyframes yellow {
to { background-color: yellow; }
}
@keyframes green {
to { background-color: green; }
}
@keyframes cyan {
to { background-color: cyan; }
}
@keyframes blue {
to { background-color: blue; }
}
@keyframes violet {
to { background-color: violet; }
}
<div></div>
How to calculate percentages in LESS CSS?
According to the LESS CSS website, you need to change the order of your equation
The output is pretty much what you expect—LESS understands the difference between colors and units. If a unit is used in an operation, like in:
@var: 1px + 5;
LESS will use that unit for the final output—
6px
in this case.
It should be:
width: 100%*(140/620);
SCSS stop keyframe SVG circle animation based on percentage
The total length of a path in SVG can be found using the getTotalLength method. In your case you could also use the formula for the perimeter of a circle (2*Math.PI*r).
Anyway you need to know the length of the path you want to animate which in this case is 445.
stroke-dasharray: 445;
stroke-dashoffset: 445;
If you want to stop the animation at 50% this means you have to stop it at 445 / 2 = 222.5
@keyframes stroke {
to {
stroke-dashoffset: 222.5;
}
}
Next come the demo. I hope it helps.
svg { border: 1px solid;}
.circle--static circle { stroke-dasharray: 4; animation: stroke 2s ease-out forwards;}
.circle--animated { top: 0; left: 0; /*position: absolute;*/}.circle--animated circle { stroke-dasharray: 445; stroke-dashoffset: 445; animation: stroke 6s ease-out forwards; animation-iteration-count: infinite;}@keyframes stroke { to { stroke-dashoffset: 222.5; }}@keyframes fadeIn { to { opacity: 1; }}
<svg height="180" width="180" class="circle--static"> <circle cx="100" cy="100" r="71" stroke="#cde9db" stroke-width="6" fill-opacity="0" /></svg>
<svg height="180" width="180" class="circle--animated"> <circle id="kk" cx="100" cy="100" r="71" stroke="#68c087" stroke-width="10" fill-opacity="0" /></svg>
Get/set current @keyframes percentage/change keyframes
I achieved roughly what I wanted using pure javascript with my CSS3.
For my experiment to come up with a way to do these objectives, I created a basic CSS3 animation rotating a circle around a small circular path. My goal was to, when a button was clicked, change the origin of the animation to the new location
To achieve the first goal of getting the percentage value of the animation I simply approximated the current percentage using the following setInterval
, which displays the approximated current percent. This runs every 40 milliseconds to correspond with the duration of the animation (milliseconds / 100)
var result = document.getElementById('result'), currentPercent = 0;
var showPercent = window.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 40);
Note on this solution:
- It is not perfect due because the counter keeps running when another tab is clicked but the animation stops, so they become un-synchronized
- It is also faulty when the button is clicked long after the last click. Evidently the setInterval runs a little bit longer than the animation, so they become less and less synced each iteration
- I looked everywhere for a more perfect solution but have been unable to come up with one as of yet
To achieve the second goal of setting a new definition for an animation's % value it took a bit of a more complex solution. After pouring through dozens of articles and web pages (the important ones listed below), I managed to come up with the following solution:
var circle = document.getElementById('circle'),
button = document.getElementById('button');
var result = document.getElementById('result'),
totalCurrentPercent = 0,
currentPercent = 0;
var showPercent = window.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 40);
function findKeyframesRule(rule) {
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
for (var j = 0; j < ss[i].cssRules.length; ++j) {
if (ss[i].cssRules[j].type == window.CSSRule.WEBKIT_KEYFRAMES_RULE && ss[i].cssRules[j].name == rule) { return ss[i].cssRules[j]; }
}
}
return null;
}
function change(anim) {
var keyframes = findKeyframesRule(anim),
length = keyframes.cssRules.length;
var keyframeString = [];
for(var i = 0; i < length; i ++)
{
keyframeString.push(keyframes[i].keyText);
}
var keys = keyframeString.map(function(str) {
return str.replace('%', '');
});
totalCurrentPercent += currentPercent;
if(totalCurrentPercent > 100)
{
totalCurrentPercent -= 100;
}
var closest = getClosest(keys);
var position = keys.indexOf(closest),
firstPercent = keys[position];
for(var i = 0, j = keyframeString.length; i < j; i ++)
{
keyframes.deleteRule(keyframeString[i]);
}
var multiplier = firstPercent * 3.6;
keyframes.insertRule("0% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 0) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 0) + "deg); background-color:red; }");
keyframes.insertRule("13% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 45) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 45) + "deg); }");
keyframes.insertRule("25% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 90) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 90) + "deg); }");
keyframes.insertRule("38% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 135) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 135) + "deg); }");
keyframes.insertRule("50% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 180) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 180) + "deg); }");
keyframes.insertRule("63% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 225) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 225) + "deg); }");
keyframes.insertRule("75% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 270) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 270) + "deg); }");
keyframes.insertRule("88% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 315) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 315) + "deg); }");
keyframes.insertRule("100% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 360) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 360) + "deg); }");
circle.style.display = "inherit";
circle.style.webkitAnimationName = anim;
window.clearInterval(showPercent);
currentPercent = 0;
showPercent = self.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 40);
}
button.onclick = function() {
circle.style.webkitAnimationName = "none";
circle.style.display = "none";
setTimeout(function () {
change("rotate");
}, 0);
}
function getClosest(keyframe) {
var curr = keyframe[0];
var diff = Math.abs (totalCurrentPercent - curr);
for (var val = 0; val < keyframe.length; val++) {
var newdiff = Math.abs(totalCurrentPercent - keyframe[val]);
if (newdiff < diff) {
diff = newdiff;
curr = keyframe[val];
}
}
return curr;
}
Check out the experiment itself here including comments describing what each part of the javascript is doing
Notes on this solution:
- I tried to make the change function as non-hard-coded as possible
- It works alright for approximating the current
@keyvalue
percentage - The transition from one animation to the other is only as smooth as however close the nearest % value of the animation is to the current % of the animation, so adding more % definitions to the animation would make it even more smooth
In the process of trying to come up with a solution for the problem, I found these useful resources:
- RussellUresti's answer in this SO post and corresponding example was quite influential and greatly aided my final solution
- To get the closest value based on an input and array values, I used paxdiablo's method in this SO post (thank you)
- This plugin, while I didn't use it myself, seemed to achieve a very similar (though seemingly not quite as customizable) effect in jQuery
---EDIT---
If you are just using CSS transitions or you can change your animation to just use transitions instead, you can use this simpler approach. You can pause the transition by copying the attributes changed by the transition, setting the attributes to those changed attributes, and then removing the class that animates it. Of course if you are using the same object to animate/pause you will need to animate the first click then pause it the next click. You can easily do the pure javascript equivalent as well
Note: the !important
in the CSS changed attribute is necessary unless you have a more leveled selector for the animation class than the jQuery selector, aka something like div #defaultID.animationClass {
as opposed to just #defaultID.animationClass {
. Since #defaultID
and #defaultID.animationClass
are both one level, this example requires the !important
--Another Edit--
For more information on this topic, check out my post on CSS-Tricks
Can I loop this keyframes animation somehow with Sass?
If you just want the animation to loop add this to style:
animation-iteration-count:infinite;
Or replace the animation name with:
animation: slidySlidesFadeInOut 180s infinite;
And this does not require SASS, although it is completely valid in SASS.
How can I use calculated values as keyframes?
TL;DR
You can't. Use a preprocessor like sass
. SASS is cool, CSS is not.
The Problem
CSS works as a templating system. You can declare a selector which then selects an element on which you are able to change it's props, right?
Yeah.. not really. You can't do whatever you like. The available selectors as of now are pretty strict in terms of dynamics. So to overcome this weakness, they introduced variables (below there's sample on how you'd use them).
:root {
--bgColor: #fafafa;
}
/*
some styling
...
*/
.my-class {
background-color: var(--bgColor);
}
Well.. not the solution you're looking for, because it only works on properties (eg. background-color
, font-size
, width
etc.) and will be evaluated at runtime. Same goes for calc()
.
The Solution
Since we cannot use dynamic variables or calculated values as selectors or as part of a selector, you cannot implement this in pure CSS. However, you can use a preprocessor such as sass
or less
to be able to use many features including string interpolation -> #{myCalculatedValue}%
CSS preprocessors transpiles the features they've provided for you into valid CSS code before the browser runs it so basically every variable you've declared (including string interpolation, but excluding CSS vars since it wasn't provided by the preprocessor) will be transpiled into a static value which CSS can use (eg. 1/0.6%
will evaluate to 1.67%
). It doesn't really matter what you're doing as long as it transpiles. You can also mix the two. Example: in sass
you can use CSS variables as well as SCSS variables.
Requirements
This is great. We have dynamic values, we have loops, we have functions. All we need is a compiler or transpiler that turns our scss
, sass
or less
code into real CSS for the browsers to be able to read it and apply.
If you're using a frontend framework (such as Angular, React or Vue), you don't have to do anything besides initializing the project with scss
styling.
Otherwise you'll need to have something like node-sass or sass, but you'll have to transpile it manually before you start your server.
node-sass
is the best IMO eg. if you use server-side rendering, but there's several sass transpiler on npm.
EDIT: You can find several transpilers on npm.
BONUS TIP: If you're using VSCode, there's an extension called Live Sass Compiler(ritwickdey.live-sass
) which, as the name suggests, will compile your sass
files to css
files automatically (same can be achieved with the --watch
tag if you're using a transpiler).
Related Topics
Shiny Dashboadpage Lock Dashboardheader on Top
Use Flexbox and Maintain an Aspect Ratio Even Though Content Is Sized Differently
Ionic - Center Text Vertically in Item-List with Item-Avatar Class
Add Strikethrough to Checked Checkbox
Custom Font Sometimes Renders in Italics in Ie8/Ie7
How to Make Tinymce's Modal Dialogs Responsive
Unbalanced CSS Columns in Chrome
Twitter Bootstrap Accordion and Button Dropdown Overflow Issue
CSS Rules for Webkit Based Browsers
CSS Transform Square into Thinner Rhombus
Is There a Way for a Particular Div to Ignore It's Parent Div's Positioning
Web Fonts and Providing Fallback Fonts
Add Additional Box-Shadow to an Element Where Existing Shadow Is Unknown
How to Write a Media Query in CSS
Fix Div to Bottom Without Using CSS Position
How to Display a Logo with CSS
Django: Bootstrap Cdn or Loading Bootstrap Files from Local Server