I am busy during this period, which is the peak season of my industry, so I have no time to update. In this article, I will use SCSS+GSAP to achieve a login effect of screen fingerprint. Let’s take a look at the effect picture first
Began to write
Step 1: Convert fingerprint rendering to SVG,
I traced the fingerprint using the pen tool in Adobe Illustrator and got this set of paths:
<svg width='180' height='320'>
<path d="M46.1, 214.3 c0, 0-4.7-15.6, 4.1-33.3"/>
<path d=",18.2 M53.5, 176.8 c0, 0-30.3, 57.5-13.7"/>
<path d=",19.1 M115.8, 166.5 c0, 0, 8.7, 19.6, 38.4,"/>
<path d=",3.1 M47.3, 221 c0, 0-2.1, 4.1 4.6 s - 5.7-20.2, 7-36.7,22.2 c8.5-11-19,37.9-15.3"/>
<path d="M102.2, c10.2 165.4, 2.7, 19.5, 10.4, 23.5, 20.2 c6.2, 15.2, 4.9, 27.1, 4.1, 39.4,"/>
<path d="M51.1 c3.3 226.5-2.7, 5.1-6.3, 5.7 10.5 c0.5-4-0.3-7.7-0.3-11.7"/>
<path d="M129.3, 200.1"/>
<path d="M56.3 c3.1 197.9-16.8, 17.6-29.9, 35.1 28.8 c17.7, 1.1, 30.9, 14.9, 32.8, 32.2,"/>
<path d="M124.2, c0.5 207.9, 9.3, 0.5, 18.7, 2.1, 27.7,"/>
<path d="M54.2 c2.1 231.1-2.6, 4.6-5.1, 6.3-8 c4. 2-6.8, 0.9-14.8, 1.5-22.3 c0.5-7.1, 3.4-16.3, 10.4-19.7"/>
<path d="M77.9 c9.3 178.2-5.1, 22.9-4.7, 30.5, 3.3,"/>
<path d=",13.6 M113, 186.5 c0, 0, 18.9, 1,54.8"/>
<path d=",5.7 M57.3, 235.2 c0, 0-3.8, 9-12.3"/>
<path d="M111.7, 231.5 c0, 0-4.1, 11.5-5.7, 13.6,"/>
<path d="M61.8 c9.3 239.4-8.4, 12.7-19.7, 11.8-31.9 - c - 0.9-12.7, 3.8-20.6, 18.5-21.2"/>
<path d=",13,11.3 M97.3, c8.4 188.1, 2.7, 11, 20.8 c0.4, 11.8-2.5, 23.7-7.9, 34.1-0.1 c, 0.1-0.1, 0.2-0.2, 0.3 C - 0.4, 0.8-0.8, 1.5-1.2, 2.3-0.5 c, 0.8 1,1.7-1.5, 2.5,"/>
<path d=",15.3 M66.2, 242.5 c0, 0-11.1, 13.6-34.9"/>
<path d="M78.7 c1.5 202.5-4.6, 3.8-9.4, 8.9-10.6 c13.5-3.2, 15.7, 13.3, 14.6, 22.1,"/>
<path d="M102.2, 219.7 c0, 0-1.7, 15.6-10.5, 28.4,"/>
<path d=",8.8 M72, 244.9 c0, 0-9.9, 9.9-15.7"/>
<path d="M84.5, 223 c0. 3-2.6, 0.5-5.2, 0.7-7.8 c0.1-2.1, 0.2-4.6-0.1-6.8 - c - 0.3-2.2-1.1-4.3-0.9-6.5 c0.5-4.4, 7.2-6.9, 10.1 3.1 c1.7, 2.2, 1.7 , 5.3, 1.9, 7.9 c0.4, 3.8, 0.3, 7.6, 0,11.4,10.8 c - 1-5.4, 21-11.5, 29.9,"/>
<path d="M90, 201.2 c0, 0,4.6, 28.1-11.4, 45.2,"/>
<path d="M67.3, 219 c65, 188.1, 78180 us, 92.7, 180.3 c18.3, 2,23.7, 18.3, 20,46.7"/>
</svg>
Copy the code
Effect:
Step 2: Animate
I’ll highlight the important parts here, and you can refer to the demo for the full code.
Fill in the fingerprint: Let’s create an HTML structure for the phone’s screen and fingerprint.
<div class="demo">
<div class="demo__screen demo__screen--clickable">
<svg class="demo__fprint" viewBox="0 0 180 320">
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M46.1.214.3c0,0-4.7-15.6.4.1-33.3"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M53.5.176.8c0,0.18.2-30.3.57.5-13.7"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M115.8.166.5c0,0.19.1.8.7.19.6.38.4"/>
</svg>
Copy the code
So far, the style is pretty simple. Note that I used Sass throughout the demo — and helped us get some heavier work done.
$scale: 1.65; $purplish-color: #8742cc; $pinkish-color: #a94a8c; $bg-color: #372546; .demo { background: linear-gradient(45deg, lighten($pinkish-color, 10%), lighten($purplish-color, 10%)); min-height: 100vh; display: flex; justify-content: center; align-items: center; font-size: 0; user-select: none; overflow: hidden; position: relative; &__screen { position: relative; background-color: $bg-color; overflow: hidden; flex-shrink: 0; &--clickable { cursor: pointer; -webkit-tap-highlight-color: transparent; }} &__fprint-path {stroke-width: 2.5px; stroke-linecap: round; fill: none; stroke: white; visibility: hidden; 0.5 s help ease the transition: opacity; &--pinkish { stroke: $pinkish-color; } &--purplish { stroke: $purplish-color; } } &__fprint { width: 180px * $scale; height: 320px * $scale; position: relative; top: 20px * $scale; overflow: visible; background-image: url('fprintBackground.svg'); background-size: cover; &--no-bg { background-image: none; }}}Copy the code
Now comes the hard part: making fingerprints interactive. This is how I will populate each individual path.
Create a class that describes path elements to make it easier to manipulate paths later.
class Path { constructor(selector, index) { this.index = index; this.querySelection = document.querySelectorAll(selector)[index]; this.length = this.querySelection.getTotalLength(); this.$ = $(selector).eq(index); this.setDasharray(); this.removesForwards = this.$.hasClass('demo__fprint-path--removes-forwards'); } setDasharray() { this.$.css('stroke-dasharray', `${this.length} ${this.length + 2}`); return this; } offset(ratio) { this.$.css('stroke-dashoffset', -this.length * ratio + 1); return this; } makeVisible() { this.$.css('visibility', 'visible'); return this; }}Copy the code
The general idea is this: Create an instance of this class for each path in our fingerprint and modify them every frame. Paths will start with an offset ratio of -1 (completely invisible), and then increase the offset ratio (we’ll call it “offset” here) at a constant value every frame until they reach 0 (fully visible). The fill animation will end at this point.
Also deal with the user stopping clicking or pressing the mouse button. In this case, we will set the animation in the opposite direction (subtracting a constant value from the offset of each frame until it reaches -1 again).
Create a function to calculate the offset increment for each frame – we’ll use it later.
function getPropertyIncrement(startValue, endValue, transitionDuration) {
// Animate at 60 FPS
const TICK_TIME = 1000 / 60;
const ticksToComplete = transitionDuration / TICK_TIME;
return (endValue - startValue) / ticksToComplete;
}
Copy the code
Step 3: Animate
We store the fingerprint path in an array:
let fprintPaths = [];
// Create a Path instance for each existing Path.
// We will first make the path invisible, and after the JavaScript runs, we will make it invisible in the CSS. So we can cancel them out and make them visible.
for (let i = 0; i < $(fprintPathSelector).length; i++) {
fprintPaths.push(new Path(fprintPathSelector, i));
fprintPaths[i].offset(-1).makeVisible();
}
Copy the code
We’ll iterate through the array for each frame in the animation, animating the path:
let fprintTick = getPropertyIncrement(0.1, TIME_TO_FILL_FPRINT);
function fprintFrame(timestamp) {
// If the time is less than 1000/65ms, I start drawing from the last frame (since there are higher refresh rate screens out there, we want all devices to look the same). Why did I use 65 instead of 60, because on a 60 Hz screen you might skip frames.
if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
lastRafCallTimestamp = timestamp;
curFprintPathsOffset += fprintTick * fprintProgressionDirection;
offsetAllFprintPaths(curFprintPathsOffset);
}
// If the animation is not finished, schedule the next frame
if (curFprintPathsOffset >= -1 && curFprintPathsOffset <= 0) {
isFprintAnimationInProgress = true;
window.requestAnimationFrame(fprintFrame);
}
// After the animation ends. We can schedule the next animation step
else if (curFprintPathsOffset > 0) {
curFprintPathsOffset = 0;
offsetAllFprintPaths(curFprintPathsOffset);
isFprintAnimationInProgress = false;
isFprintAnimationOver = true;
// Remove the background with a grey path
$fprint.addClass('demo__fprint--no-bg');
// Schedule the next animation step - convert one of the paths to a string
startElasticAnimation();
// Schedule fingerprint removal
window.requestAnimationFrame(removeFprint);
}
// The fingerprint returns when the user releases the click
else if (curFprintPathsOffset < -1) {
curFprintPathsOffset = -1;
offsetAllFprintPaths(curFprintPathsOffset);
isFprintAnimationInProgress = false; }}Copy the code
We’ll attach some event listeners to the demo:
$screen.on('mousedown touchstart'.function() {
fprintProgressionDirection = 1;
// If the animation is already in progress, we will not schedule the next frame because it is already scheduled in 'fprintFrame'. Also, if the animation is already over, we obviously don't schedule it. That's why we have two separate flags for these conditions, right
if(! isFprintAnimationInProgress && ! isFprintAnimationOver)window.requestAnimationFrame(fprintFrame);
})
// On 'mouseup'/' touchend ', we flip the animation direction
$(document).on('mouseup touchend'.function() {
fprintProgressionDirection = -1;
if(! isFprintAnimationInProgress && ! isFprintAnimationOver)window.requestAnimationFrame(fprintFrame);
})
Copy the code
Now we should finish the click part! Presentation:
Step 4: Remove fingerprints
This section is very similar to the crafting section, except that we have to take into account the fact that some paths are removed in one direction and the rest in another. That’s why we added the process-forward modifier earlier.
First, we’ll have two additional arrays: one for the forward delete path and one for the backward delete path:
const fprintPathsFirstHalf = [];
const fprintPathsSecondHalf = [];
for (let i = 0; i < $(fprintPathSelector).length; i++) {
// ...
if (fprintPaths[i].removesForwards)
fprintPathsSecondHalf.push(fprintPaths[i]);
else
fprintPathsFirstHalf.push(fprintPaths[i]);
}
Copy the code
We’ll write a function that offsets them in the right direction:
function offsetFprintPathsByHalves(ratio) {
fprintPathsFirstHalf.forEach(path= > path.offset(ratio));
fprintPathsSecondHalf.forEach(path= > path.offset(-ratio));
}
Copy the code
We also need a function to draw the frame:
function removeFprintFrame(timestamp) {
// If we go faster than 65 FPS, we may lose frames
if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
curFprintPathsOffset += fprintTick * fprintProgressionDirection;
offsetFprintPathsByHalves(curFprintPathsOffset);
lastRafCallTimestamp = timestamp;
}
// If the animation is not finished, schedule the next frame
if (curFprintPathsOffset >= -1)
window.requestAnimationFrame(removeFprintFrame);
else {
// Due to a floating point error, the final offset may be slightly less than -1, so if it exceeds that value, we will assign -1 to it and animate another frame
curFprintPathsOffset = -1; offsetAllFprintPaths(curFprintPathsOffset); }}function removeFprint() {
fprintProgressionDirection = -1;
window.requestAnimationFrame(removeFprintFrame);
}
Copy the code
Now all that’s left is for removeFprint to call after we’ve finished fingerprint filling:
function fprintFrame(timestamp) {
// ...
else if (curFprintPathsOffset > 0) {
// ...
window.requestAnimationFrame(removeFprint);
}
// ...
}
Copy the code
The result looks like this:
Step 5: End of animation path
As you can see, some of its paths are longer than they started out with the fingerprint almost removed. I move them to different paths and start animating at the appropriate time. I could merge them into existing paths, but that would be much more difficult and would make almost no difference at 60fps.
<path class="demo__ending-path demo__ending-path--pinkish" d="M48.4, 220 c - 5.8 -, 4.2-6.9, 11.5, 7.6, 18.1-0.8 c, 6.7-0.9, 14.9, 9.9, 12.4 c - 9.1-2.5-14.7-5.4-19.9-13.4 - c - 3.4-5.2-0.4-12.3, 2.3-17.2 - c 3.2-5.9, 6.8-13 c3.5,14.5-11.6, 0.6, 7.7, 3.4, 4.5, 7.1,"/>
Copy the code
Basic style:
&__ending-path {
fill: none;
stroke-width: 2.5 px.;
stroke-dasharray: 60 1000;
stroke-dashoffset: 61;
stroke-linecap: round;
will-change: stroke-dashoffset, stroke-dasharray, opacity;
transform: translateZ(0);
transition: stroke-dashoffset 1s ease, stroke-dasharray 0.5 s linear, opacity 0.75 s ease;
&--removed {
stroke-dashoffset: -130;
stroke-dasharray: 5 1000;
}
&--transparent {
opacity: 0;
}
&--pinkish {
stroke: $pinkish-color;
}
&--purplish {
stroke: $purplish-color; }}Copy the code
Added the –removed modifier to flow into these paths at the appropriate time:
function removeFprint() {
$endingPaths.addClass('demo__ending-path--removed');
setTimeout(() = > {
$endingPaths.addClass('demo__ending-path--transparent');
}, TIME_TO_REMOVE_FPRINT * 0.9);
// ...
}
Copy the code
Fingerprint effect completed:
Final step: fingerprint distortion
Here we need to use GSAP’s morphSVG plug-in
Create a path and a line that will become our string keyframes:
<line id='demo__straight-path' x1="0" y1="151.3" x2="180" y2="151.3"/>
<path class="demo__hidden-path" id='demo__arc-to-top' d="M0, c62.3 148.4-13.5, 122.3-13.5, 180, 0"/>
Copy the code
Then we’ll use morphSVG to convert paths between keyframes:
const $elasticPath = $('#demo__elastic-path'); const ELASTIC_TRANSITION_TIME_TO_STRAIGHT = 250; const WOBBLE_TIME = 1000; function startElasticAnimation() { $elasticPath.css('stroke-dasharray', 'none'); const elasticAnimationTimeline = new TimelineLite(); elasticAnimationTimeline .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_STRAIGHT / 1000, { delay: TIME_TO_REMOVE_FPRINT / 1000 * 0.7, morphSVG: '#demo__arc-to-top' }) .to('#demo__elastic-path', WOBBLE_TIME / 1000, { morphSVG: '#demo__straight-path', ease: Elastic. EaseOut. Config (1, 0.3)})}Copy the code
FprintFrame Once the fingerprint is filled, we’ll call this function internally:
function fprintFrame(timestamp) {
// ...
else if (curFprintPathsOffset > 0) {
// ...
startElasticAnimation();
// ...
}
// ...
}
Copy the code
Final effect completed:
Complete code HTML:
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>CodePen - css-t. part 4</title>
<meta name="viewport" content="width=device-width, minimum-scale=1.0">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="demo">
<div class="demo__screen demo__screen--clickable">
<svg class="demo__fprint" viewBox="0 0 180 320">
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M46.1,214.3c0,0-4.7-15.6,4.1-33.3"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M53.5,176.8c0,0,18.2-30.3,57.5-13.7"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M115.8,166.5c0,0,19.1,8.7,19.6,38.4"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M47.3,221c0,0,3.1-2.1,4.1-4.6s-5.7-20.2,7-36.7c8.5-11,22.2-19,37.9-15.3"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M102.2,165.4c10.2,2.7,19.5,10.4,23.5,20.2c6.2,15.2,4.9,27.1,4.1,39.4"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M51.1,226.5c3.3-2.7,5.1-6.3,5.7-10.5c0.5-4-0.3-7.7-0.3-11.7"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M56.3,197.9c3.1-16.8,17.6-29.9,35.1-28.8c17.7,1.1,30.9,14.9,32.8,32.2"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--purplish" d="M124.2,207.9c0.5,9.3,0.5,18.7-2.1,27.7"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M54.2,231.1c2.1-2.6,4.6-5.1,6.3-8c4.2-6.8,0.9-14.8,1.5-22.3c0.5-7.1,3.4-16.3,10.4-19.7"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M77.9,178.2c9.3-5.1,22.9-4.7,30.5,3.3"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--purplish" d="M113,186.5c0,0,13.6,18.9,1,54.8"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M57.3,235.2c0,0,5.7-3.8,9-12.3"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M111.7,231.5c0,0-4.1,11.5-5.7,13.6"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M61.8,239.4c9.3-8.4,12.7-19.7,11.8-31.9c-0.9-12.7,3.8-20.6,18.5-21.2"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M97.3,188.1c8.4,2.7,11,13,11.3,20.8c0.4,11.8-2.5,23.7-7.9,34.1c-0.1,0.1-0.1,0.2-0.2,0.3
c-0.4,0.8-0.8,1.5-1.2,2.3c-0.5,0.8-1,1.7-1.5,2.5"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M66.2,242.5c0,0,15.3-11.1,13.6-34.9"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M78.7,202.5c1.5-4.6,3.8-9.4,8.9-10.6c13.5-3.2,15.7,13.3,14.6,22.1"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M102.2,219.7c0,0-1.7,15.6-10.5,28.4"/>
<path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M72,244.9c0,0,8.8-9.9,9.9-15.7"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M84.5,223c0.3-2.6,0.5-5.2,0.7-7.8c0.1-2.1,0.2-4.6-0.1-6.8c-0.3-2.2-1.1-4.3-0.9-6.5c0.5-4.4,7.2-6.9,10.1-3.1
c1.7,2.2,1.7,5.3,1.9,7.9c0.4,3.8,0.3,7.6,0,11.4c-1,10.8-5.4,21-11.5,29.9"/>
<path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--purplish" d="M90,201.2c0,0,4.6,28.1-11.4,45.2"/>
<path class="demo__fprint-path demo__fprint-path--pinkish" id='demo__elastic-path' d="M67.3,219C65,188.1,78,180.1,92.7,180.3c18.3,2,23.7,18.3,20,46.7"/>
<line id='demo__straight-path' x1="0" y1="151.3" x2="180" y2="151.3"/>
<path class="demo__hidden-path" id='demo__arc-to-top' d="M0,148.4c62.3-13.5,122.3-13.5,180,0"/>
<path class="demo__ending-path demo__ending-path--pinkish"d="M48.4,220c-5.8,4.2-6.9,11.5-7.6,18.1c-0.8,6.7-0.9,14.9-9.9,12.4c-9.1-2.5-14.7-5.4-19.9-13.4c-3.4-5.2-0.4-12.3,2.3-17.2c3.2-5.9,6.8-13,14.5-11.6c3.5,0.6,7.7,3.4,4.5,7.1"/>
<path class="demo__ending-path demo__ending-path--pinkish"d="M57.3,235.2c-14.4,9.4-10.3,19.4-17.8,21.1c-5.5,1.3-8.4-7.8-13.8-4.2c-2.6,1.7-5.7,7.7-4.6,10.9c0.7,2,4.1,2,5.8,2.4c3,0.7,8.4,3,7.6,7.2c-0.6,3-5,5.3-2.4,8.7c1.8,2.2,4.7,1.1,6.9,0.3c11.7-4.3,14.5,0.8,16.5,0.9"/>
<path class="demo__ending-path demo__ending-path--purplish"d="M79,246c-1.8,2.4-4.9,2.9-7.6,3.2c-2.7,0.3-5.8-0.8-7.7,1.6c-2.9,3.3,0.7,8.2-1.2,12c-1.5,2.8-4.5,2.4-6.9,1.3c-10.1-4.7-33.2-17.5-38.1-2.5c-1.1,3.4-1.9,7.5-1.3,11c0.6,4,5.6,7.9,7.7,2.3c0.8-2.1,3.1-8.6-1-8.9"/>
<path class="demo__ending-path demo__ending-path--pinkish"d="M91.8,248c0,0-3.9,6.4-6.2,9.2c-3.8,4.5-7.9,8.9-11.2,13.8c-1.9,2.8-4.4,6.4-3.7,10c0.9,5.2,4.7,12.5,9.7,14.7c5.2,2.2,15.9-4.7,13.1-10.8c-1.4-3-6.3-7.9-10-7.2c-1,0.2-1.8,1-2,2"/>
<path class="demo__ending-path demo__ending-path--purplish"d="M114.8,239.4c-2.7,6.1-8.3,12.8-7.8,19.8c0.3,4.6,3.8,7.4,7.8,9.1c8.9,3.8,19.7,0.4,28.6-1.3c8.8-1.7,19.7-3.2,23.7,6.7c2.8,6.8,6.1,14.7,4.4,22.2"/>
<path class="demo__ending-path demo__ending-path--pinkish"d="M129.9,224.2c-0.4,7.5-3.1,18,0.7,25c2.8,5.1,14.3,6.3,19.5,7.4c3.7,0.7,8.7,2.2,12-0.5c6.7-5.4,11.1-13.7,14.1-21.6c3.1-8-4.4-12.8-11.1-14.5c-5-1.3-19.1-0.7-21-6.7c-0.9-2.8,1.8-5.9,3.4-7.9"/>
</svg>
</div>
</div>
<!-- partial -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/MorphSVGPlugin.min.js?r=182'></script><script src="./script.js"></script>
</body>
</html>
Copy the code
CSS:
*, *:before, *:after { box-sizing: border-box; margin: 0; padding: 0; } .demo { background: linear-gradient(45deg, #bd69a3, #a16ad7); min-height: 100vh; display: flex; justify-content: center; align-items: center; font-size: 0; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; overflow: hidden; position: relative; } .demo__screen { position: relative; background-color: #372546; overflow: hidden; flex-shrink: 0; } .demo__screen--clickable { cursor: pointer; -webkit-tap-highlight-color: transparent; }. Demo__fprint-path {stroke-width: 2.5px; stroke-linecap: round; fill: none; stroke: white; visibility: hidden; 0.5 s help ease the transition: opacity; will-change: stroke-dashoffset, stroke-dasharray; transform: translateZ(0); } .demo__fprint-path--pinkish { stroke: #a94a8c; } .demo__fprint-path--purplish { stroke: #8742cc; } .demo__fprint-path#demo__elastic-path { will-change: opacity; opacity: 1; } .demo__hidden-path { display: none; } .demo__fprint { width: 297px; height: 528px; position: relative; top: 33px; overflow: visible; background-image: url("fprintBackground.svg"); background-size: cover; } .demo__fprint--no-bg { background-image: none; } .demo__ending-path { fill: none; Stroke - width: 2.5 px; stroke-dasharray: 60 1000; stroke-dashoffset: 61; stroke-linecap: round; will-change: stroke-dashoffset, stroke-dasharray, opacity; transform: translateZ(0); Transition: stroke-Dashoffset 1s ease, stroke-Dasharray 0.5s Linear, opacity 0.75s ease; } .demo__ending-path--removed { stroke-dashoffset: -130; stroke-dasharray: 5 1000; } .demo__ending-path--transparent { opacity: 0; } .demo__ending-path--pinkish { stroke: #a94a8c; } .demo__ending-path--purplish { stroke: #8742cc; }Copy the code
JS:
$(document).ready(function() { if (! window.requestAnimationFrame) { window.requestAnimationFrame = function(cb) { setTimeout(() => cb(new Date()), 1000/60); } } const TIME_TO_FILL_FPRINT = 700; //ms const TIME_TO_REMOVE_FPRINT = 250; const ELASTIC_TRANSITION_TIME_TO_STRAIGHT = 250; const WOBBLE_TIME = 1000; const fprintPathSelector = '.demo__fprint-path'; const $fprintPaths = $('.demo__fprint-path'); const $fprint = $('.demo__fprint'); const $screen = $('.demo__screen'); const $endingPaths = $('.demo__ending-path'); const $elasticPath = $('#demo__elastic-path'); let isFprintAnimationInProgress = false; let isFprintAnimationOver = false; let curFprintPathsOffset = -1; let fprintProgressionDirection = 1; let lastRafCallTimestamp = 0; let fprintTick = getPropertyIncrement(0, 1, TIME_TO_FILL_FPRINT); let fprintPaths = []; const fprintPathsFirstHalf = []; const fprintPathsSecondHalf = []; for (let i = 0; i < $(fprintPathSelector).length; i++) { fprintPaths.push(new Path(fprintPathSelector, i)); fprintPaths[i].offset(-1).makeVisible(); if (fprintPaths[i].removesForwards) fprintPathsSecondHalf.push(fprintPaths[i]); else fprintPathsFirstHalf.push(fprintPaths[i]); } function fprintFrame(timestamp) { if (timestamp - lastRafCallTimestamp >= 1000 / 65) { lastRafCallTimestamp = timestamp; curFprintPathsOffset += fprintTick * fprintProgressionDirection; offsetAllFprintPaths(curFprintPathsOffset); } if (curFprintPathsOffset >= -1 && curFprintPathsOffset <= 0) { isFprintAnimationInProgress = true; window.requestAnimationFrame(fprintFrame); } else if (curFprintPathsOffset > 0) { curFprintPathsOffset = 0; offsetAllFprintPaths(curFprintPathsOffset); isFprintAnimationInProgress = false; isFprintAnimationOver = true; $fprint.addClass('demo__fprint--no-bg'); startElasticAnimation(); fprintTick = getPropertyIncrement(0, 1, TIME_TO_REMOVE_FPRINT); window.requestAnimationFrame(removeFprint); } else if (curFprintPathsOffset < -1) { curFprintPathsOffset = -1; offsetAllFprintPaths(curFprintPathsOffset); isFprintAnimationInProgress = false; } } function removeFprint() { $endingPaths.addClass('demo__ending-path--removed'); setTimeout(() => { $endingPaths.addClass('demo__ending-path--transparent'); }, TIME_TO_REMOVE_FPRINT * 0.9); fprintProgressionDirection = -1; window.requestAnimationFrame(removeFprintFrame); } function removeFprintFrame(timestamp) { if (timestamp - lastRafCallTimestamp >= 1000 / 65) { curFprintPathsOffset += fprintTick * fprintProgressionDirection; offsetFprintPathsByHalves(curFprintPathsOffset); lastRafCallTimestamp = timestamp; } if (curFprintPathsOffset >= -1) window.requestAnimationFrame(removeFprintFrame); else { curFprintPathsOffset = -1; offsetAllFprintPaths(curFprintPathsOffset); } } function startElasticAnimation() { $elasticPath.css('stroke-dasharray', 'none'); const elasticAnimationTimeline = new TimelineLite(); elasticAnimationTimeline .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_STRAIGHT / 1000, { delay: TIME_TO_REMOVE_FPRINT / 1000 * 0.7, morphSVG: '#demo__arc-to-top' }) .to('#demo__elastic-path', WOBBLE_TIME / 1000, { morphSVG: '#demo__straight-path', ease: Elastic.easeOut.config(1, ForEach (path => path.offset(ratio)); } function offsetFprintPathsByHalves(ratio) { fprintPathsFirstHalf.forEach(path => path.offset(ratio)); fprintPathsSecondHalf.forEach(path => path.offset(-ratio)); } $screen.on('mousedown touchstart', function() { fprintProgressionDirection = 1; if (! isFprintAnimationInProgress && ! isFprintAnimationOver) window.requestAnimationFrame(fprintFrame); }) $(document).on('mouseup touchend', function() { fprintProgressionDirection = -1; if (! isFprintAnimationInProgress && ! isFprintAnimationOver) window.requestAnimationFrame(fprintFrame); }); }); function getPropertyIncrement(startValue, endValue, transitionDuration) { const TICK_TIME = 1000 / 60; const ticksToComplete = transitionDuration / TICK_TIME; return (endValue - startValue) / ticksToComplete; } class Path { constructor(selector, index) { this.index = index; this.querySelection = document.querySelectorAll(selector)[index]; this.length = this.querySelection.getTotalLength(); this.$ = $(selector).eq(index); this.setDasharray(); this.removesForwards = this.$.hasClass('demo__fprint-path--removes-forwards'); } setDasharray() { this.$.css('stroke-dasharray', `${this.length} ${this.length + 2}`); return this; } offset(ratio) { this.$.css('stroke-dashoffset', -this.length * ratio + 1); return this; } makeVisible() { this.$.css('visibility', 'visible'); return this; }}Copy the code