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
$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
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>');
}
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)});
}
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.
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);
}