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