How do you implement animation along a path in theory? - animation

I am looking at offset-path in CSS, where you essentially do this:
* {
box-sizing: border-box;
}
:root {
--delay: 0ms;
--path: path("M.4 76.8C102-24.9 266.9-24.9 368.5 76.8c81.3 81.3 81.3 213.2 0 294.5-65.1 65.1-170.6 65.1-235.6 0-52.1-52.1-52.1-136.5 0-188.5 41.6-41.6 109.2-41.6 150.8 0 33.3 33.3 33.3 87.3 0 120.6-26.7 26.7-69.9 26.7-96.5 0-21.3-21.3-21.3-55.9 0-77.2 17.1-17.1 44.7-17.1 61.8 0 13.6 13.6 13.6 35.8 0 49.4-10.9 10.9-28.6 10.9-39.5 0-8.7-8.7-8.7-22.9 0-31.6 7-7 18.3-7 25.3 0");
}
body {
margin: 0;
padding: 2rem;
min-height: 100vh;
background-color: #1b1b24;
display: flex;
justify-content: center;
align-items: center;
}
.wrapper {
position: relative;
}
.obj {
--color: salmon;
position: absolute;
top: 0;
left: 0;
offset-path: var(--path);
animation: move 4500ms infinite ease-in-out var(--delay);
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background-color: var(--color);
opacity: 0;
transform: scale(0);
}
.obj--2 {
--delay: 1500ms;
--color: hotpink;
}
.obj--3 {
--delay: 3000ms;
--color: turquoise;
}
svg {
width: 429px;
}
#keyframes appear {
100% {
opacity: 1;
}
}
#keyframes move {
10% {
opacity: 1;
offset-distance: 0%;
transform: scale(1);
}
30% {
box-shadow: -0.5rem 0 0.3rem var(--color, white);
}
70% {
box-shadow: -0.5rem 0 0.3rem var(--color, white);
}
90% {
opacity: 1;
offset-distance: 100%;
transform: scale(0.2);
box-shadow: none;
}
100% {
opacity: 0;
offset-distance: 100%;
transform: scale(0.2);
}
}
<div class="wrapper">
<div class="obj"></div>
<div class="obj obj--2"></div>
<div class="obj obj--3"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 431.7 422.6"><path d="M1.1 77.8c101.7-101.7 266.5-101.7 368.2 0 81.3 81.3 81.3 213.2 0 294.5-65.1 65.1-170.6 65.1-235.6 0-52.1-52.1-52.1-136.5 0-188.5 41.6-41.6 109.2-41.6 150.8 0 33.3 33.3 33.3 87.3 0 120.6-26.7 26.7-69.9 26.7-96.5 0-21.3-21.3-21.3-55.9 0-77.2 17.1-17.1 44.7-17.1 61.8 0 13.6 13.6 13.6 35.8 0 49.4-10.9 10.9-28.6 10.9-39.5 0-8.7-8.7-8.7-22.9 0-31.6 7-7 18.3-7 25.3 0" fill="none" stroke="#5e5e7d" stroke-width="3" stroke-miterlimit="10"/></svg>
</div>
They have this variable:
--path: path("M.4 76.8C102-24.9 266.9-24.9 368.5 76.8c81.3 81.3 81.3 213.2 0 294.5-65.1 65.1-170.6 65.1-235.6 0-52.1-52.1-52.1-136.5 0-188.5 41.6-41.6 109.2-41.6 150.8 0 33.3 33.3 33.3 87.3 0 120.6-26.7 26.7-69.9 26.7-96.5 0-21.3-21.3-21.3-55.9 0-77.2 17.1-17.1 44.7-17.1 61.8 0 13.6 13.6 13.6 35.8 0 49.4-10.9 10.9-28.6 10.9-39.5 0-8.7-8.7-8.7-22.9 0-31.6 7-7 18.3-7 25.3 0");
Somehow that gets converted into some underlying data-structure, and then every frame, you somehow move along that "path". How does that work, how do you implement that at a high level?
Basically, I imagine that you have somehow converted the SVG d path into a vector (array of points?), but no that doesn't seem right because some points are not along the curve (control points). Then somehow, every tick/update of the clock, it moves the x and y position of the object so it is further along the path. I am not making the mental leap/connection to see how this could be implemented though. Any insight?
I guess part of the question is, how is the path implemented under the hood? And how do you calculate the next position every tick of the clock along the path/curve? The CSS abstracts all that away somehow, and I would like to know the inner workings of it.

This may be of help:
function setAnimationsProgress(insTime) {
let i = 0;
const animations = instance.animations;
const animationsLength = animations.length;
while (i < animationsLength) {
const anim = animations[i];
const animatable = anim.animatable;
const tweens = anim.tweens;
const tweenLength = tweens.length - 1;
let tween = tweens[tweenLength];
// Only check for keyframes if there is more than one tween
if (tweenLength) tween = filterArray(tweens, t => (insTime < t.end))[0] || tween;
const elapsed = minMax(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration;
const eased = isNaN(elapsed) ? 1 : tween.easing(elapsed);
const strings = tween.to.strings;
const round = tween.round;
const numbers = [];
const toNumbersLength = tween.to.numbers.length;
let progress;
for (let n = 0; n < toNumbersLength; n++) {
let value;
const toNumber = tween.to.numbers[n];
const fromNumber = tween.from.numbers[n] || 0;
if (!tween.isPath) {
value = fromNumber + (eased * (toNumber - fromNumber));
} else {
value = getPathProgress(tween.value, eased * toNumber, tween.isPathTargetInsideSVG);
}
if (round) {
if (!(tween.isColor && n > 2)) {
value = Math.round(value * round) / round;
}
}
numbers.push(value);
}
// Manual Array.reduce for better performances
const stringsLength = strings.length;
if (!stringsLength) {
progress = numbers[0];
} else {
progress = strings[0];
for (let s = 0; s < stringsLength; s++) {
const a = strings[s];
const b = strings[s + 1];
const n = numbers[s];
if (!isNaN(n)) {
if (!b) {
progress += n + ' ';
} else {
progress += n + b;
}
}
}
}
setProgressValue[anim.type](animatable.target, anim.property, progress, animatable.transforms);
anim.currentValue = progress;
i++;
}
}
function getPathProgress(path, progress, isPathTargetInsideSVG) {
function point(offset = 0) {
const l = progress + offset >= 1 ? progress + offset : 0;
return path.el.getPointAtLength(l);
}
const svg = getParentSvg(path.el, path.svg)
const p = point();
const p0 = point(-1);
const p1 = point(+1);
const scaleX = isPathTargetInsideSVG ? 1 : svg.w / svg.vW;
const scaleY = isPathTargetInsideSVG ? 1 : svg.h / svg.vH;
switch (path.property) {
case 'x': return (p.x - svg.x) * scaleX;
case 'y': return (p.y - svg.y) * scaleY;
case 'angle': return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
}
}
There it is computing the path point every animation step. Don't know how to explain it further.

Related

What should I use with sass to make this code run well?

$frameSize: 100 / $chCount;
#keyframes type {
#for $ch from 1 to $chCount {
$frame: $ch * $frameSize;
#{$frame}% {
width: $ch * $chWidth;
}
}
100% {
width: ($chCount - 1) * $chWidth;
padding-right: 0;
}
}
this is some code which I took from codepen for animating text, but this section of the code was not valid in vscode even in SASS

How to use sass string replace in data:image string?

I'm using string replace mentioned on CSS Tricks
But I'm trying to do string replace within background image data:image/svg+xml string
I'm am trying to replace one of my color vars hex # with url encoded %23.
I don't think the stack snippet works with SASS but getting same results in jsfiddle.
You can see better in sassmeister the css output https://www.sassmeister.com/gist/7cf11bf6f3ee4951cf67e0e6074d1d67
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
#return $string;
}
$wm-black : #202020;
.modal-close {
&:before {
background: {
image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 320 512"><style>.a{fill:%23' + str-replace( $wm-black, '#', '%23' ) + '}</style><path class="a" d="M193.94 256L296.5 153.44l21.15-21.15c3.12-3.12 3.12-8.19 0-11.31l-22.63-22.63c-3.12-3.12-8.19-3.12-11.31 0L160 222.06 36.29 98.34c-3.12-3.12-8.19-3.12-11.31 0L2.34 120.97c-3.12 3.12-3.12 8.19 0 11.31L126.06 256 2.34 379.71c-3.12 3.12-3.12 8.19 0 11.31l22.63 22.63c3.12 3.12 8.19 3.12 11.31 0L160 289.94 262.56 392.5l21.15 21.15c3.12 3.12 8.19 3.12 11.31 0l22.63-22.63c3.12-3.12 3.12-8.19 0-11.31L193.94 256z"/></svg>');
repeat: no-repeat;
position: calc(100% - 25px) 25px;
size: 36px 36px;
color: rgba(#000,.95);
}
opacity: 1;
transition: opacity .25s ease;
content: '';
display: block;
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 99;
}
}
<button class="modal-close"></button>
In PHP storm, the string parameter var is not working as it should so not valid.
My code currently fails to compile, so something is up but not sure why str-replace function wont return here.
Error code.
Module build failed (from ./node_modules/css-loader/index.js):
ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: $string: #202020 is not a string.
╷
4 │ $index: str-index($string, $search);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
╵
src/scss/_functions.scss 4:11 str-replace()
Thanks
Update: I've also tried this method, syntax looks better but still not compiling.
See it compiling in Sassmeister... https://www.sassmeister.com/gist/c9569c0373b52f0a9b3f8ae07860f8af
Interpolate and convert the color value into a string:
#{str-replace('' + $wm-black, '#', '%23')}
Example usage...
$wm-black : #202020;
.bg-svg {
background-image: url('data:image/svg+xml;utf8,<svg><style>.a{fill:#{str-replace('' + $wm-black, '#', '%23')};}</style></svg>');
}
Thanks #MiguelPynto for your answer, this is my solution which I am currently using, but same principle.
#{str-replace(#{$wm-black}, '#', '%23')}
Example usage below...
$wm-black : #202020;
.bg-svg {
background-image: url('data:image/svg+xml;utf8,<svg><style>.a{fill:#{str-replace(#{$wm-black},'#','%23')};}</style></svg>');
}

Interpolate and add percentage for transform: Invalid property value

I'm trying to interpolate a value and add it into a transform translate string like this:
#for $x from 1 through 30 {
#for $y from 1 through 30 {
$xt: calc(#{$x} * 100%);
$yt: calc(#{$y} * 100%);
.stone-0#{$x}0#{$y} {
transform: translate(#{$xt}, -#{$yt});
}
}
}
But the output I'm getting is:
.stone-0204 {
transform: translate(calc(2 * 100%), -calc(4 * 100%));
}
How can I get the output:
.stone-0204 {
transform: translate(200%, -400%);
}
Solved with:
.stone-0#{$x}0#{$y} {
transform: translate(percentage($x), -#{percentage($y)});
}

Dividing percentage variable in Sass?

I have a variable which is a number and a % eg 10%. How can I use it as a value in my SASS but apply a division on it?
I have this:
$value: 0.1;
$value-percent: $value * 1%;
$value-from-50: (50 - $value) * 1%;
.test {
padding-left: $value-percent;
}
.test2 {
width: $value-from-50;
}
Which outputs this:
.test {
padding-left: 10%;
}
.test2 {
width: 40%;
}
What I now need to do is apply half of the value of $value-percent:
.test3 {
padding-left: $value-percent / 2;
}
So that I can output:
.test3 {
width: 5%;
}
Ive tried various combinations of that example code with normal and curly brackets. I can get the correct number of 10 outputted into the CSS but the % is always missing from it.
If your initial var isn't a percentage and is just a number you may need to try this:
.test {
padding-right: ($var / 2) + 0%
}
Which is better practice as it'll convert the value you pass it into what you're adding it to, in this case a percentage.

LESS: Loop using data stored in an array (or something similar)

I found this example in LESS documentation:
LESS:
.generate-columns(4);
.generate-columns(#n, #i: 1) when (#i =< #n) {
.column-#{i} {
width: (#i * 100% / #n);
}
.generate-columns(#n, (#i + 1));
}
OUTPUT CSS:
.column-1 {
width: 25%;
}
.column-2 {
width: 50%;
}
.column-3 {
width: 75%;
}
.column-4 {
width: 100%;
}
This loop generates 4 different divs because '4' was the value passed by firs mixin's call, but generated values are entirely calculated inside mixin. In other words, #n implicitly indicates "number of iterations".
I would like to be able to set a sort of "array of values" such as:
PSEUDO-CODE:
.generate-columns( [25,50,75,100] );
that should be passed to loop mixin and then generates the same CSS code, using each array's value. Is it possible?
.generate-columns-loop(#i; #widths) when (#i <= length(#widths)) {
.column-#{i} {
#width: extract(#widths, #i);
width: (#width * 1%);
}
.generate-columns-loop((#i + 1), #widths);
}
.generate-columns(#widths...) {
.generate-columns-loop(1, #widths);
}
.generate-columns(25, 50, 75, 100);
You can pass an array list to the mixin and then use the extract function to extract the value corresponding to the iteration number and use it.
.generate-columns(#n: 4; #list: 10, 20, 30, 40 );
.generate-columns(#n; #i: 1; #list) when (#i =< #n) {
.column-#{i} {
width: percentage((extract(#list, #i)/100)); /* built-in function to convert numeric to percentage */
}
.generate-columns(#n; (#i + 1); #list);
}
or like below (basically same functionality as the above one with the only difference being that in the above snippet we are using named parameters feature because we are skipping providing a value for the #i variable in the mixin call.)
#widths: 10, 20, 30, 40, 50;
.generate-columns(5; 1; #widths);
.generate-columns(#n; #i: 1; #list) when (#i =< #n) {
.column-#{i} {
width: percentage((extract(#list, #i)/100));
}
.generate-columns(#n; (#i + 1); #list);
}

Resources