Javascript: CSS-Animationen erneut starten, starten wenn im Viewport

Animierte Roboter: CSS Animation restart und animation play state

Ohne besonderes auslösendes Event (z.B. hover oder focus) starten CSS Keyframes-Animationen automatisch, wenn das Dokument im Browserfenster geladen wird. CSS-Animationen haben ihre Grenzen, die aber schon mit einer kleinen Dosis Javascript aufgehoben werden.

23-02-02 SITEMAP

CSS-Keyframes-Animation erneut starten

CSS bietet nur wenige Ereignisse, die eine Animation auslösen, kann Animationen nur begrenzt neu starten und kann @keyframes-Animationen nur über den animation-delay miteinander synchronisieren. Erst Javascript synchronisiert komplexe CSS-Animationen, kann CSS keyframes-Animationen neu starten oder die Animation auslösen, wenn die Animation in den Viewport kommt.

Wenn eine CSS-Animation beim Laden der Seite ausgeführt und abgelaufen ist, kann sie nicht ohne Weiteres erneut gestartet werden.

<svg viewBox="0 0 742 874" height="100%" width="100%">
	<path class="draw" stroke="darkslategray" fill="none" stroke-width="10" 
	          d="m371.4 138a142.5 142.5 0 0 0 -142.4 142.8 142.5 142.5 … -142.8z"/>
</svg>
Linien zeichnen mit SVG Path-Animation: Lineart mit CSS

Das CSS für die Animation besteht zwei @keyframes: die Linie, die sich um die Schachfiguren legt und der Button, der die Animation neu startet, aber erst nach Ablauf der Animation sichtbar wird (animation-delay – Verzögerung beim Starten der Animation).

.draw {
   stroke-dasharray: 2338.5;
   stroke-dashoffset: 2338.5;
   animation: dash 3s linear forwards;
}
@keyframes dash {
   to { stroke-dashoffset: 0 }
}

#restart {                ┌── animation-delay
	opacity: 0;            ▼
	animation: fadeIn 0.5s 3s forwards;
}

@keyframes fadeIn {
	to { opacity: 1 }
}

Um die Animation nach Ablauf erneut zu starten, überschreibt JavaScript animationName mit "none". Damit verliert das Element die Verbindung zur @keyframes-Regel. Der direkte Aufruf von requestAnimationFrame löscht den gerade eingesetzten style sofort (innerhalb eines Animation-Frames) und setzt so die ursprüngliche @keyframes-Regel wieder in Kraft.

const restart = document.querySelector ("#restart");

restart.addEventListener ("click", function () {
	const draw = document.querySelector (".draw")
	draw.style.animationName = "none";
	requestAnimationFrame (() => {
		draw.style.animationName = "";
	});
	
	restart.style.animationName = "none";
	requestAnimationFrame (() => {
		restart.style.animationName = "";
	});
});

Von Hover zu Touchstart

Das Hovern der Maus hat keine Entsprechung auf Touchscreens: Es gibt kein :touch. Es ist nicht einmal möglich, die Reaktion vorauszusagen: Vielleicht reagiert der Touchscreen auf CSS :hover, vielleicht nicht. Vielleicht friert der Effekt auf dem Touchscreen ein. :hover ist für Benutzer mit der Maus immer noch ein intuitiver Auslöser, aber :hover allein reicht nicht.

<script>
const container = document.querySelector(".container");
container.ontouchstart = function () {
   this.classList.add("touch");
}
</script>

Dafür war also die kleine Zeile .touch .animated-sky im CSS der Animation …

CSS Animation mit animation-play-state starten und stoppen

animation-play-state : paused versetzt eine Animation in eine Wartezustand, bis ein Event mit animation-play-state: running die Sequenz der Keyframes auslöst.

Eine Animation mit animation-play-state: paused wird zwar schon beim Laden der Seite gestartet, sie pausiert aber sofort.

#banner { 
    position: relative; 
}

.ball { 
    position: absolute;
    animation: rolerball 4s alternate infinite paused; 
}
 
@keyframes rolerball {
    from {transform: translateX(0) rotate(0deg)}
    to {transform: translateX(200%) rotate(720deg)}
}

Wird die Animation durch :hover gestartet, läuft sie solange die Maus über dem Element hovert. Sobald die Maus das animerte Element verlässt, springt das Element ruckzuck abrupt zurück auf seine Position vor der Animation.

In vielen Situationen wirkt es natürlicher, wenn die Animation an der Stelle stehenbleibt, an der die Maus das Element verlässt.

let banner = document.querySelector('#banner');

banner.onmouseover = 
banner.ontouchstart = function (evt) {
    this.children[0].style.animationPlayState = 'running';
}
banner.onmouseout = 
banner.ontouchend = function (evt) {
    this.children[0].style.animationPlayState = 'paused';
}

Das spielt die Animation ab, solange die Maus über dem Banner hovert. Verlässt sie das Banner, pausiert die Animation und wird beim nächsten Hovern an dieser Stelle wieder aufgenommen.

Starten mit mouseover und touch

Der CSS-Pseudo-Selektor :hover löst eine CSS-transition oder Keyframe-Animation aus. Auf Touch-Screens gibt es kein zuverlässiges hover. Das touch-Event gibt es wiederum nicht für's CSS.

Das lösen ein paar Zeilen Javascript:

let box = document.querySelectorAll('.box');
for (let i=0; i<box.length; i++) {
	box[i].onmouseover = 
	box[i].ontouch = function () {
		this.classList.add('toggle');
		this.firstElementChild.setAttribute('style','opacity:1');
	}
	box[i].onmouseout = 
	box[i].ontouchend = function () {
		this.classList.remove('toggle');
		this.firstElementChild.setAttribute('style','opacity:0.3');
	}
}

Javascript classList schaltet eine Klasse hinzu oder entfernt sie. classList wird allerdings erst ab IE 10 unterstützt. Wenn IE 9 noch mitgezogen werden muss, gibt es ein classList-Polyfil auf Github.

CSS Animation im Browserfester sichtbar?

Eine Animation starten, wenn sie in den Viewport kommt – das war früher das Ding für weit ausholende Plugins oder Libraries wie jQuery. Der Javascript Intersection Observer wirkt in allen modernen Browsern (außer IE11, der nicht mehr modern und ad acta gelegt ist), ist ein Leichtgewicht und belastet das Scrollen nicht.

CSS Animation im Browserfenster sichtbar? css transition

Die Animation selbst ruht auf einer einfachen CSS-Animation.

.stift{
    animation: shift 3s forwards;
}

@keyframes shift {
    from { transform: translateY(600px) }
    to   { transform: translateY(0px) }
}

Der Stift hat allerdings keine CSS-Klasse stift – die kommt erst später ins Spiel.

<div class="inview">
	…
	<g id="stift">
	…
	</g>
	…
</div>

Das Script beobachtet das Element mit der CSS-Klasse inview (let inview = document.querySelector(".inview")), legt die Optionen für den Start der Animation fest: root: null, rootMargin: "0px". threshold ist die Überschneidung des Elements mit dem sichtbaren Ausschnitt des Browserfensters. Mit threshold=1 würde die Animation erst starten, wenn das Element vollständig zu sehen ist.

Erst wenn das Element inview ins Browserfenster gescrollt wird (observed.isIntersecting), fügt das Script dem Stift die Klasse .stift hinzu.

<script>
let inview = document.querySelector(".inview");

const options = {
	root: null,
	rootMargin: "0px",
	threshold: 0.7,
};

const callback = function (entries, observer) {
	let observed = entries[0];
	if (observed.isIntersecting) {
		document.querySelector("#stift").classList.add("stift");
	}
}

const observer = new IntersectionObserver (callback, options);

if (inview) {
	observer.observe (inview);
}
</script>