Ich bin vor einiger Zeit auf diesen erstaunlichen Dribbble-Shot von Jakub Reis gestoßen. Er hat meine Aufmerksamkeit erregt und ich wusste, dass ich ihn unbedingt in Code nachbilden musste. Damals wusste ich nicht wie. Ich habe viele verschiedene Dinge ausprobiert und etwa ein Jahr später habe ich es endlich geschafft, diese Demo zu erstellen.
Ich habe auf dem Weg ein paar Dinge gelernt, also lassen Sie mich Sie auf eine kleine Reise mitnehmen, was ich getan habe, um dies zu erstellen, weil Sie vielleicht auch das eine oder andere lernen.
Sehen Sie den Pen Öffnungsbildschirm für eine Banking-App von Kirill Kiyutin (@kiyutink) auf CodePen.
Schritt 1: Teilen Sie die Arbeit in Teile auf
Ich habe das Original-GIF viele Male angesehen. Mein Ziel war es, die Animation in kleine, verdauliche Häppchen zu zerlegen, und ich konnte sie wie folgt aufteilen:
- Füllen Sie den Fingerabdruck
- Entfernen Sie den Fingerabdruck
- Animieren Sie die Pfadenden
- Morphieren Sie eine der gekrümmten Fingerabdrucklinien in eine horizontale
- Animieren Sie die "Kugel", die von der Linie abgeschossen wird
- Morphieren Sie die Zeichenkette in ein Diagramm
- Animieren Sie kleine explodierende Partikel
- Animieren Sie den Kontostand
- Und andere kleine Animationen mit einigen kleineren CSS-Übergängen
Ich weiß, es sieht viel aus – aber wir können das schaffen!
Schritt 2: Nehmen Sie die Original-Demo Frame für Frame auseinander
Ich musste so viele Informationen wie möglich aus dem Original-GIF extrahieren, um ein gutes Verständnis der Animation zu bekommen. Daher habe ich es in einzelne Frames zerlegt. Es gibt tatsächlich viele Dienste, die das für uns tun können. Ich habe einen auf ezgif.com verwendet, aber es könnte genauso gut etwas anderes gewesen sein. So oder so ermöglicht uns dies, Details wie Farben, Größen und Proportionen aller verschiedenen Elemente zu erhalten, die wir erstellen müssen.
Oh, und wir müssen den Fingerabdruck immer noch in ein SVG umwandeln. Auch hier gibt es genügend Apps, die uns dabei helfen. Ich habe Adobe Illustrator verwendet, um den Fingerabdruck mit dem Zeichenstift zu verfolgen, um diesen Pfadsatz zu erhalten
Sehen Sie den Pen css-t. Pfade von Kirill Kiyutin (@kiyutink) auf CodePen.
Wir werden den gleichen Prozess mit dem Liniendiagramm durchlaufen, das gegen Ende der Animation erscheint, also können wir diesen Vektoreditor geöffnet lassen. 🙂
Schritt 3: Implementieren Sie die Animationen
Ich werde erklären, wie die Animationen im endgültigen Pen funktionieren, aber Sie können auch einige der erfolglosen Ansätze, die ich im Laufe der Zeit verfolgt habe, am Ende des Artikels finden.
Ich werde mich hier auf die wichtigen Teile konzentrieren und Sie können sich für den vollständigen Code auf die Demos beziehen.
Füllen des Fingerabdrucks
Lassen Sie uns die HTML-Struktur des Telefonbildschirms und des Fingerabdrucks erstellen.
<div class="demo">
<div class="demo__screen demo__screen--clickable">
<svg class="demo__fprint" viewBox="0 0 180 320">
<!-- removes-forwards and removes-backwards classes will be helpful later on -->
<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"/>
<!-- ... and about 20 more paths like this -->
</svg>
Die Stile sind bisher ziemlich einfach. Beachten Sie, dass ich im gesamten Demo Sass verwende – ich finde, es hilft, die Arbeit sauber zu halten und erleichtert einige der schwereren Arbeiten, die wir tun müssen.
// I use a $scale variable to quickly change the scaling of the whole pen, so I can focus on the animation and decide on the size later on.
$scale: 1.65;
$purplish-color: #8742cc;
$pinkish-color: #a94a8c;
$bg-color: #372546;
// The main container
.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;
// The screen that holds the login component
&__screen {
position: relative;
background-color: $bg-color;
overflow: hidden;
flex-shrink: 0;
&--clickable {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
}
// Styles the fingerprint SVG paths
&__fprint-path {
stroke-width: 2.5px;
stroke-linecap: round;
fill: none;
stroke: white;
visibility: hidden;
transition: opacity 0.5s ease;
&--pinkish {
stroke: $pinkish-color;
}
&--purplish {
stroke: $purplish-color;
}
}
// Sizes positions the fingerprint SVG
&__fprint {
width: 180px * $scale;
height: 320px * $scale;
position: relative;
top: 20px * $scale;
overflow: visible;
// This is going to serve as background to show "unfilled" paths. we're gonna remove it at the moment where the filling animation is over
background-image: url('https://kiyutink.github.io/svg/fprintBackground.svg');
background-size: cover;
&--no-bg {
background-image: none;
}
}
}
Nun zum schwierigen Teil: den Fingerabdruck interaktiv zu machen. Sie können hier über die Animation von SVG-Linien lesen. Das ist die Methode, die wir verwenden werden, um jeden einzelnen Pfad zu füllen.
Lassen Sie uns eine Klasse erstellen, die ein Pfadelement beschreibt, damit die Pfade später einfacher manipuliert werden können.
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;
}
}
Die allgemeine Idee ist folgende: Erstellen Sie eine Instanz dieser Klasse für jeden Pfad, den wir im Fingerabdruck haben, und modifizieren Sie ihn in jedem Frame. Die Pfade beginnen mit einem Offset-Verhältnis von -1 (vollständig unsichtbar) und erhöhen dann das Offset-Verhältnis (das wir von nun an als "Offset" bezeichnen werden) in jedem Frame um einen konstanten Wert, bis sie 0 (vollständig sichtbar) erreichen. Die Füllanimation ist zu diesem Zeitpunkt beendet.
Wenn Sie noch nie etwas mit diesem Frame-für-Frame-Ansatz animiert haben, hier ist eine sehr einfache Demo, die Ihnen hilft zu verstehen, wie das funktioniert
Sehen Sie den Pen 60fps raf Animationsproof of Concept von Kirill Kiyutin (@kiyutink) auf CodePen.
Wir sollten auch den Fall behandeln, dass der Benutzer aufhört zu tippen oder die Maustaste gedrückt hält. In diesem Fall animieren wir in die entgegengesetzte Richtung (wir subtrahieren in jedem Frame einen konstanten Wert vom Offset, bis er wieder -1 erreicht).
Lassen Sie uns die Funktion erstellen, die die Offset-Inkremente für jeden Frame berechnet – das wird später nützlich sein.
function getPropertyIncrement(startValue, endValue, transitionDuration) {
// We animate at 60 fps
const TICK_TIME = 1000 / 60;
const ticksToComplete = transitionDuration / TICK_TIME;
return (endValue - startValue) / ticksToComplete;
}
Nun ist es Zeit zu animieren! Wir werden die Fingerabdruckpfade in einem einzigen Array behalten
let fprintPaths = [];
// We create an instance of Path for every existing path.
// We don't want the paths to be visible at first and then
// disappear after the JavaScript runs, so we set them to
// be invisible in CSS. That way we can offset them first
// and then make them visible.
for (let i = 0; i < $(fprintPathSelector).length; i++) {
fprintPaths.push(new Path(fprintPathSelector, i));
fprintPaths[i].offset(-1).makeVisible();
}
Wir werden dieses Array für jeden Frame der Animation durchgehen und die Pfade einzeln animieren
let fprintTick = getPropertyIncrement(0, 1, TIME_TO_FILL_FPRINT);
function fprintFrame(timestamp) {
// We don't want to paint if less than 1000 / 65 ms elapsed
// since the last frame (because there are faster screens
// out there and we want the animation to look the same on
// all devices). We use 65 instead of 60 because, even on
// 60 Hz screens, `requestAnimationFrame` can sometimes be called
// a little sooner, which can result in a skipped frame.
if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
lastRafCallTimestamp = timestamp;
curFprintPathsOffset += fprintTick * fprintProgressionDirection;
offsetAllFprintPaths(curFprintPathsOffset);
}
// Schedule the next frame if the animation isn't over
if (curFprintPathsOffset >= -1 && curFprintPathsOffset <= 0) {
isFprintAnimationInProgress = true;
window.requestAnimationFrame(fprintFrame);
}
// The animation is over. We can schedule next animation steps
else if (curFprintPathsOffset > 0) {
curFprintPathsOffset = 0;
offsetAllFprintPaths(curFprintPathsOffset);
isFprintAnimationInProgress = false;
isFprintAnimationOver = true;
// Remove the background with grey paths
$fprint.addClass('demo__fprint--no-bg');
// Schedule the next animation step - transforming one of the paths into a string
// (this function is not implemented at this step yet, but we'll do that soon)
startElasticAnimation();
// Schedule the fingerprint removal (removeFprint function will be implemented in the next section)
window.requestAnimationFrame(removeFprint);
}
// The fingerprint is back to the original state (the user has stopped holding the mouse down)
else if (curFprintPathsOffset < -1) {
curFprintPathsOffset = -1;
offsetAllFprintPaths(curFprintPathsOffset);
isFprintAnimationInProgress = false;
}
}
Und wir werden einige Event-Listener an die Demo anhängen
$screen.on('mousedown touchstart', function() {
fprintProgressionDirection = 1;
// If the animation is already in progress,
// we don't schedule the next frame since it's
// already scheduled in the `fprintFrame`. Also,
// we obviously don't schedule it if the animation
// is already over. That's why we have two separate
// flags for these conditions.
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);
})
...und jetzt sollten wir mit dem ersten Schritt fertig sein! So sieht unsere Arbeit in diesem Schritt aus
Sehen Sie den Pen css-t. Schritt 1 von Kirill Kiyutin (@kiyutink) auf CodePen.
Entfernen des Fingerabdrucks
Dieser Teil ist dem ersten ziemlich ähnlich, nur dass wir jetzt berücksichtigen müssen, dass sich einige Pfade in eine Richtung entfernen und der Rest in die andere. Deshalb haben wir früher den Modifikator --removes-forwards hinzugefügt.
Zuerst haben wir zwei zusätzliche Arrays: eines für die Pfade, die nach vorne entfernt werden, und ein weiteres für die, die nach hinten entfernt werden
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]);
}
...und wir schreiben eine Funktion, die sie in die richtige Richtung verschiebt
function offsetFprintPathsByHalves(ratio) {
fprintPathsFirstHalf.forEach(path => path.offset(ratio));
fprintPathsSecondHalf.forEach(path => path.offset(-ratio));
}
Wir werden auch eine Funktion benötigen, die die Frames zeichnet
function removeFprintFrame(timestamp) {
// Drop the frame if we're faster than 65 fps
if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
curFprintPathsOffset += fprintTick * fprintProgressionDirection;
offsetFprintPathsByHalves(curFprintPathsOffset);
lastRafCallTimestamp = timestamp;
}
// Schedule the next frame if the animation isn't over
if (curFprintPathsOffset >= -1)
window.requestAnimationFrame(removeFprintFrame);
else {
// Due to the floating point errors, the final offset might be
// slightly less than -1, so if it exceeds that, we'll just
// assign -1 to it and animate one more frame
curFprintPathsOffset = -1;
offsetAllFprintPaths(curFprintPathsOffset);
}
}
function removeFprint() {
fprintProgressionDirection = -1;
window.requestAnimationFrame(removeFprintFrame);
}
Jetzt müssen wir nur noch removeFprint aufrufen, wenn wir mit dem Füllen des Fingerabdrucks fertig sind
function fprintFrame(timestamp) {
// ...
else if (curFprintPathsOffset > 0) {
// ...
window.requestAnimationFrame(removeFprint);
}
// ...
}
Schauen wir uns unsere Arbeit jetzt an
Sehen Sie den Pen css-t. Teil 2 von Kirill Kiyutin (@kiyutink) auf CodePen.
Animieren der Pfadenden
Sie sehen, dass einige der Pfade länger sind, als sie ursprünglich waren, da der Fingerabdruck fast entfernt ist. Ich habe sie in separate Pfade verschoben, die im richtigen Moment mit der Animation beginnen. Ich könnte sie in die vorhandenen Pfade integrieren, aber das wäre viel schwieriger und bei 60fps würde es kaum einen Unterschied machen.
Erstellen wir sie
<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"/>
<!-- and 5 more paths like this -->
...und wenden einige grundlegende Stile an
&__ending-path {
fill: none;
stroke-width: 2.5px;
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;
&--removed {
stroke-dashoffset: -130;
stroke-dasharray: 5 1000;
}
&--transparent {
opacity: 0;
}
&--pinkish {
stroke: $pinkish-color;
}
&--purplish {
stroke: $purplish-color;
}
}
Nun müssen wir den Modifikator --removed hinzufügen, damit diese Pfade im richtigen Moment eingefügt werden
function removeFprint() {
$endingPaths.addClass('demo__ending-path--removed');
setTimeout(() => {
$endingPaths.addClass('demo__ending-path--transparent');
}, TIME_TO_REMOVE_FPRINT * 0.9);
// ...
}
Nun nimmt unsere Arbeit wirklich Gestalt an
Sehen Sie den Pen css-t. Teil 3 von Kirill Kiyutin (@kiyutink) auf CodePen.
Morphing des Fingerabdrucks
Okay, diesen Teil fand ich sehr schwer selbst zu machen, aber mit dem morphSVG-Plugin von GSAP ist es sehr einfach zu implementieren.
Lassen Sie uns die unsichtbaren Pfade erstellen (naja, einen Pfad und eine Linie, um genau zu sein 🙂), die die Keyframes für unsere Zeichenkette sein werden
<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"/>
Dann verwenden wir morphSVG, um den Pfad zwischen den Keyframes zu überführen
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',
// I played with the easing a bit to get that "vibration" effect
ease: Elastic.easeOut.config(1, 0.3)
})
}
Wir rufen diese Funktion in fprintFrame auf, sobald der Fingerabdruck gefüllt ist
function fprintFrame(timestamp) {
// ...
else if (curFprintPathsOffset > 0) {
// ...
startElasticAnimation();
// ...
}
// ...
}
Das Ergebnis ist dies
Sehen Sie den Pen css-t. Teil 4 von Kirill Kiyutin (@kiyutink) auf CodePen.
Animieren der schwebenden Kugel
Dafür habe ich einfache, direkte CSS-Animationen verwendet. Ich habe die Timing-Funktionen so gewählt, dass sie die Schwerkraft simulieren. Sie können hier oder hier mit den Timing-Funktionen herumspielen.
Lassen Sie uns ein div erstellen
<div class="demo__bullet"></div>
...und ihm einige Stile zuweisen
&__bullet {
position: absolute;
width: 4px * $scale;
height: 4px * $scale;
background-color: white;
border-radius: 50%;
top: 210px * $scale;
left: 88px * $scale;
opacity: 0;
transition: all 0.7s cubic-bezier(0.455, 0.030, 0.515, 0.955);
will-change: transform, opacity;
// This will be applied after the bullet has descended, to create a transparent "aura" around it
&--with-aura {
box-shadow: 0 0 0 3px * $scale rgba(255, 255, 255, 0.3);
}
// This will be applied to make the bullet go up
&--elevated {
transform: translate3d(0, -250px * $scale, 0);
opacity: 1;
}
// This will be applied to make the bullet go down
&--descended {
transform: translate3d(0, 30px * $scale, 0);
opacity: 1;
transition: all 0.6s cubic-bezier(0.285, 0.210, 0.605, 0.910);
}
}
Dann binden wir es zusammen, indem wir Klassen basierend auf den Interaktionen eines Benutzers hinzufügen und entfernen
const DELAY_TO_BULLET_AURA = 300;
const ELEVATION_TIME = 700;
const DELAY_AFTER_ELEVATION = 700;
const $bullet = $('.demo__bullet');
function elevateBullet() {
$bullet.addClass('demo__bullet--elevated');
}
function descendBullet() {
$bullet.addClass('demo__bullet--descended').removeClass('demo__bullet--elevated');
animateBulletAura();
}
function animateBulletAura() {
setTimeout(() => $bullet.addClass('demo__bullet--with-aura'), DELAY_TO_BULLET_AURA);
}
function animateBullet() {
elevateBullet();
$screen.removeClass('demo__screen--clickable');
setTimeout(descendBullet, ELEVATION_TIME + DELAY_AFTER_ELEVATION);
}
Nun müssen wir die Funktion animateBullet aufrufen
function startElasticAnimation() {
// ...
animateBullet();
}
Hier sind wir an diesem Punkt
Sehen Sie den Pen css-t. Teil 5 von Kirill Kiyutin (@kiyutink) auf CodePen.
Morphing der Zeichenkette in ein Diagramm
Nun, lassen Sie uns diese Zeichenkette in ein Diagramm verwandeln, in dem die Kugel landen kann. Wir fügen der morphSVG-Animation einen weiteren Keyframe hinzu.
<path class="demo__hidden-path" id='demo__curve' d="M0,140.2c13.1-10.5,34.7-17,48.5-4.1c5.5,5.2,7.6,12.1,9.2,19.2c2.4,10.5,4.3,21,7.2,31.4c2.4,8.6,4.3,19.6,10.4,26.7c4.3,5,17.7,13.4,23.1,4.8c5.9-9.4,6.8-22.5,9.7-33c4.9-17.8,13-14.6,15.7-14.6c1.8,0,9,2.3,15.4,5.4c6.2,3,11.9,7.7,17.9,11.2c7,4.1,16.5,9.2,22.8,6.6"/>
Wir fügen diesen Keyframe wie folgt in unsere Timeline ein
const DELAY_TO_CURVE = 350;
const ELASTIC_TRANSITION_TIME_TO_CURVED = 300;
function startElasticAnimation() {
// ...
elasticAnimationTimeline
// ...
.to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_CURVED / 1000, {
delay: DELAY_TO_CURVE / 1000,
morphSVG: '#demo__curve'
})
// ...
}
Hier ist, was wir bekommen
Sehen Sie den Pen css-t. Teil 6 von Kirill Kiyutin (@kiyutink) auf CodePen.
Explodierende Partikel
Das ist eine lustige Animation. Zuerst erstellen wir ein paar neue divs, die die explodierenden Partikel enthalten
<div class="demo__logo-particles">
<div class="demo__logo-particle"></div>
<!-- and several more of these -->
</div>
<div class="demo__money-particles">
<div class="demo__money-particle"></div>
<!-- and several more of these -->
</div>
Die beiden Explosionen sind praktisch gleich, mit Ausnahme einiger Parameter. Hier sind SCSS-Mixins nützlich. Wir können die Funktion einmal schreiben und sie auf unsere divs anwenden.
@mixin particlesContainer($top) {
position: absolute;
width: 2px * $scale;
height: 2px * $scale;
left: 89px * $scale;
top: $top * $scale;
// We'll hide the whole container to not show the particles initially
opacity: 0;
&--visible {
opacity: 1;
}
}
// The $sweep parameter shows how far from the center (horizontally) the initial positions of the particles can be
@mixin particle($sweep, $time) {
width: 1.5px * $scale;
height: 1.5px * $scale;
border-radius: 50%;
background-color: white;
opacity: 1;
transition: all $time ease;
position: absolute;
will-change: transform;
// Phones can't handle the particles very well :(
@media (max-width: 400px) {
display: none;
}
@for $i from 1 through 30 {
&:nth-child(#{$i}) {
left: (random($sweep) - $sweep / 2) * $scale + px;
@if random(100) > 50 {
background-color: $purplish-color;
}
@else {
background-color: $pinkish-color;
}
}
&--exploded:nth-child(#{$i}) {
transform: translate3d((random(110) - 55) * $scale + px, random(35) * $scale + px, 0);
opacity: 0;
}
}
}
Beachten Sie den Kommentar im Code, dass die Partikel auf weniger leistungsfähigen Geräten wie Handys nicht besonders gut funktionieren. Vielleicht gibt es hier einen anderen Ansatz, der dies lösen würde, wenn jemand Ideen hat und sich einbringen möchte.
Also, lassen Sie uns die Mixins auf die Elemente anwenden
&__logo-particles {
@include particlesContainer(15px);
}
&__logo-particle {
@include particle(50, 1.7s);
}
&__money-particles {
@include particlesContainer(100px);
}
&__money-particle {
@include particle(100, 1.5s);
}
Nun fügen wir die Klassen zu den divs zur richtigen Zeit in JavaScript hinzu
const DELAY_TO_ANIMATE_MONEY_PARTICLES = 300;
const DELAY_TO_ANIMATE_LOGO_PARTICLES = 500;
const $moneyParticles = $('.demo__money-particle');
const $moneyParticlesContainer = $('.demo__money-particles');
const $logoParticlesContainer = $('.demo__logo-particles');
const $logoParticles = $('.demo__logo-particle');
function animateMoneyParticles() {
setTimeout(() => {
$moneyParticlesContainer.addClass('demo__money-particles--visible')
$moneyParticles.addClass('demo__money-particle--exploded');
}, DELAY_TO_ANIMATE_MONEY_PARTICLES);
}
function animateLogoParticles() {
setTimeout(() => {
$logoParticlesContainer.addClass('demo__logo-particles--visible')
$logoParticles.addClass('demo__logo-particle--exploded');
}, DELAY_TO_ANIMATE_LOGO_PARTICLES);
}
function elevateBullet() {
// ...
animateMoneyParticles();
animateLogoParticles();
}
Hier sind wir
Sehen Sie den Pen css-t. Teil 7 von Kirill Kiyutin (@kiyutink) auf CodePen.
Animieren des Kontostands
Jede Ziffer erhält ein paar zufällige Zahlen, durch die wir scrollen werden
<div class="demo__money">
<div class="demo__money-currency">$</div>
<!-- every digit will be a div like this one -->
<div class="demo__money-digit">
1
2
3
4
5
6
7
8
1
</div>
// ...
</div>
Wir werden den Ziffern unterschiedliche Übergangszeiten zuweisen, damit die Animationen versetzt sind. Wir können dafür eine SCSS-Schleife verwenden
&__money-digit {
// ...
// we start from 2 because the first child is the currency sign :)
@for $i from 2 through 6 {
&:nth-child(#{$i}) {
transition: transform 0.1s * $i + 0.2s ease;
transition-delay: 0.3s;
transform: translate3d(0, -26px * $scale * 8, 0);
}
&--visible:nth-child(#{$i}) {
transform: none;
}
}
}
Alles, was noch zu tun ist, ist, die CSS-Klassen zur richtigen Zeit hinzuzufügen
const $money = $('.demo__money');
const $moneyDigits = $('.demo__money-digit');
function animateMoney() {
$money.addClass('demo__money--visible');
$moneyDigits.addClass('demo__money-digit--visible');
}
function descendBullet() {
// ...
animateMoney();
// ...
}
Nun lehnen Sie sich zurück und bestaunen Sie unsere Arbeit
Sehen Sie den Pen css-t. Teil 8 von Kirill Kiyutin (@kiyutink) auf CodePen.
Die restlichen Animationen sind ziemlich einfach und beinhalten leichte CSS-Übergänge, daher werde ich nicht näher darauf eingehen, um es kurz zu halten. Den gesamten endgültigen Code finden Sie in der abgeschlossenen Demo.
Einige abschließende Worte
- In meinen frühen Versuchen habe ich versucht, CSS-Übergänge für die gesamte Animationsarbeit zu verwenden. Ich fand es praktisch unmöglich, den Fortschritt und die Richtung der Animation zu kontrollieren, also habe ich diese Idee schnell aufgegeben und etwa einen Monat gewartet, bevor ich erneut begann. In Wirklichkeit, wenn ich damals gewusst hätte, dass die Web Animations API existiert, hätte ich versucht, sie zu nutzen.
- Ich habe versucht, die Explosion mit Canvas für bessere Leistung zu realisieren (mit diesem Artikel als Referenz), aber ich fand es schwierig, die Bildrate mit zwei separaten
requestAnimationFrame-Ketten zu kontrollieren. Wenn Sie wissen, wie das geht, können Sie es mir vielleicht in den Kommentaren sagen (oder einen Artikel für CSS-Tricks schreiben). - Nachdem ich einen ersten funktionierenden Prototypen hatte, war ich mit seiner Leistung sehr unzufrieden. Ich erreichte etwa 40-50fps auf einem PC, ganz zu schweigen von Handys. Ich habe viel Zeit damit verbracht, den Code zu optimieren, und dieser Artikel war sehr hilfreich.
- Sie sehen, dass das Diagramm einen Gradienten hat. Das habe ich erreicht, indem ich einen Gradienten direkt im SVG-Defs-Block deklariert habe
<defs>
<linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#8742cc"/>
<stop offset="100%" stop-color="#a94a8c"/>
</linearGradient>
</defs>
...und ihn dann in den CSS-Eigenschaften angewendet habe
fill: url(#linear);
stroke: url(#linear);
Der gesamte Prozess von Anfang bis Ende – vom Entdecken des Dribbble-Shots bis zum Abschluss der Arbeit – dauerte etwa ein Jahr. Ich habe hier und da monatelange Pausen eingelegt, entweder weil ich nicht wusste, wie ich einen bestimmten Aspekt angehen sollte, oder weil ich einfach nicht genug Freizeit hatte, um daran zu arbeiten. Der gesamte Prozess war eine sehr wertvolle Erfahrung und ich habe unterwegs viel Neues gelernt.
Das gesagt, die wichtigste Lektion, die Sie mitnehmen sollten, ist, dass Sie keine Angst haben müssen, eine ehrgeizige Aufgabe anzunehmen oder entmutigt zu sein, wenn Sie sie zunächst nicht angehen können. Das Web ist ein großer Ort und es gibt viel Platz, um die Dinge herauszufinden, während Sie fortfahren.
Ich habe den Artikel gerne gelesen. Danke für das Teilen der Bemühungen.
Das ist fantastisch, tolle Arbeit bei der Nachbildung! Ich finde es ein wenig faszinierend und lade die Seite immer wieder neu, um es sich herumwirbeln zu sehen.
Es wäre großartig, wenn Apple eine Art TouchID Web API implementieren würde, damit dies tatsächlich funktionieren würde. Selbst ein Klick macht es zu einem ziemlich unterhaltsamen Tor zu Inhalten.
Nett! Ich habe viel daraus gelernt. Danke!
Ich habe eine Frage zur Animation Ihrer Animationen. Da getPropertyIncrement() die transitionDuration übergeben wird, scheinen Sie zu erwarten, dass die Animation in dieser Zeit abgeschlossen ist. (700ms, damit sich die Fingerabdruckpfade mit den Farben füllen). Aber wenn ich einen setTimeout hinzufüge (nach 700ms), der ausgelöst wird, sobald die Animation beginnt, mit einer Rückruffunktion, die die Animation stoppt, zeigt sich, dass die Animation nicht vollständig abgeschlossen ist.
Es scheint nicht, dass die Animation in der erwarteten Zeit abgeschlossen ist. Ich frage mich, warum das so ist
Hallo Jordan,
Das ist eine berechtigte Frage. Der Grund dafür ist, dass einige Animations-Ticks fehlen
1. Sie sehen auf Zeile 223 im endgültigen Pen, dass wir fprintFrame nicht sofort als Reaktion auf das mousedown/touchdown-Ereignis des Benutzers aufrufen. Wir verwenden tatsächlich requestAnimationFrame, um den ersten Frame zu planen, sodass 16,666 ms dort verloren gehen (ein Frame bei 60fps).
2. Wie im Artikel erwähnt, kann der Offset aufgrund von Gleitkommafehlern etwas größer als 0 sein, daher animieren wir einen weiteren Frame (Zeile 194 im endgültigen Pen) – da geht ein weiterer Frame verloren.
Abgesehen davon, wenn Sie einen Bildschirm mit einer anderen Bildwiederholrate als 60 haben, kann dies zu unterschiedlichen Zeiten führen. Obwohl ich das berücksichtigt habe, habe ich 65 fps anstelle von 60 verwendet und einige Frames können damit inkonsistent sein, da zum Beispiel 144 weder durch 60 noch durch 65 teilbar ist (viele Bildschirme haben eine Bildwiederholrate von 144 Hz).
Es ist nicht sinnvoll, die Animation mit der Genauigkeit einer Atomuhr zu erstellen (das ist aufgrund der Funktionsweise von asynchronem JS nicht einmal erreichbar). Wenn Sie sich an einen bestimmten Teil der Animation hängen müssen, sollten Sie Timeouts vermeiden, da diese dafür nicht zuverlässig sind. Fügen Sie stattdessen einen Funktionsaufruf (oder Ausdruck) an der gewünschten Stelle ein.
Eine weitere Sache, die die Zeit beeinflussen kann, ist, wie schnell der Computer ist (wenn Sie einen eher langsamen Computer haben, kann die Ausführung des synchronen Codes länger dauern als die für den Frame vorgesehene Zeit) – obwohl dies unwahrscheinlich und in den meisten Fällen vernachlässigbar ist.
Danke für Ihr Interesse am Artikel!