| name | snow-slides-switch |
| description | HTML slide page-transition animation system. Implements multiple slide-switching effects (slide-left, slide-right, slide-up, slide-down, zoom, flip, fade, blur, elastic-scale) for single-page HTML presentations, while coordinating per-element entrance animations so that the slide transition and element animations play simultaneously — never sequentially — eliminating the double-animation problem. Use for: HTML PPT/Slides authoring, single-page presentation animation systems, smooth slide transitions that do not break element entrance animations. Keywords: slides switch transition animation ppt stagger entrance overlap. |
| argument-hint | Describe the target presentation style or desired transition effect |
snow-slides-switch — HTML Slide Transition Animation System
Core Design Principle
No Double-Animation: The slide transition (page movement) and element entrance animations run simultaneously in parallel, not one after the other. Element animations begin 50 ms into the slide transition, so both complete within the same 600 ms window.
Timeline:
0ms ─── new slide starts moving in from off-screen (CSS transition)
50ms ───── per-element entrance animations begin (JS applyAnimations)
600ms ─────────── slide transition ends, page fully visible
650ms ──────────────── cleanup (remove trans-* class, hide old slide)
I. Complete CSS
1. Stage Layout
#app {
position: relative;
width: var(--slide-w);
height: var(--slide-h);
perspective: 2000px;
}
.slide {
position: absolute;
inset: 0;
display: none;
transition:
transform 0.6s cubic-bezier(.4, 0, .2, 1),
opacity 0.6s ease,
filter 0.6s ease;
}
.slide.active {
display: flex;
}
2. Off-Screen States (starting position before a slide enters)
.slide.trans-left { transform: translateX(-100%); opacity: 0; }
.slide.trans-right { transform: translateX(100%); opacity: 0; }
.slide.trans-up { transform: translateY(-100%); opacity: 0; }
.slide.trans-down { transform: translateY(100%); opacity: 0; }
.slide.trans-zoom { transform: scale(.7); opacity: 0; }
.slide.trans-scale-e { transform: scale(.3); opacity: 0; }
.slide.trans-flip { transform: rotateY(90deg); opacity: 0; }
.slide.trans-fade { opacity: 0; }
.slide.trans-blur { opacity: 0; filter: blur(12px); }
3. Active States (adding .active triggers the CSS transition into view)
.slide.trans-left.active,
.slide.trans-right.active,
.slide.trans-up.active,
.slide.trans-down.active {
transform: translate(0);
opacity: 1;
}
.slide.trans-zoom.active { transform: scale(1); opacity: 1; }
.slide.trans-scale-e.active { transform: scale(1); opacity: 1; }
.slide.trans-flip.active { transform: rotateY(0); opacity: 1; }
.slide.trans-fade.active { opacity: 1; }
.slide.trans-blur.active { opacity: 1; filter: blur(0); }
4. Element Entrance Keyframes
@keyframes fadeInStagger {
from { opacity: 0; transform: translateY(30px) scale(.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes fadeInUp { from { opacity:0; transform:translateY(40px) } to { opacity:1; transform:translateY(0) } }
@keyframes fadeInLeft { from { opacity:0; transform:translateX(-50px) } to { opacity:1; transform:translateX(0) } }
@keyframes fadeInRight { from { opacity:0; transform:translateX(50px) } to { opacity:1; transform:translateX(0) } }
@keyframes scaleIn { from { opacity:0; transform:scale(.5) } to { opacity:1; transform:scale(1) } }
@keyframes blurIn { from { opacity:0; filter:blur(12px) } to { opacity:1; filter:blur(0) } }
@keyframes zoomInUp { from { opacity:0; transform:scale(.6) translateY(40px) } to { opacity:1; transform:scale(1) translateY(0) } }
5. Continuous Animation Classes (must be reset on each slide enter)
@keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.06)} }
@keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-12px)} }
.pulse { animation: pulse 2.5s ease-in-out infinite; }
.float { animation: float 3s ease-in-out infinite; }
.float-slow { animation: float 5s ease-in-out infinite; }
II. Complete JavaScript
let current = 0;
let transitioning = false;
const slides = document.querySelectorAll('.slide');
const total = slides.length;
const TRANSITIONS = [
'trans-left', 'trans-right', 'trans-up', 'trans-down',
'trans-zoom', 'trans-fade', 'trans-blur', 'trans-scale-e'
];
const CONTINUOUS_ANIM_CLASSES = [
'.pulse', '.pulse-slow', '.pulse-glow', '.pulse-glow-slow',
'.float', '.float-slow', '.float-rotate',
'.heartbeat', '.heartbeat-slow',
'.breathe', '.subtle-glow', '.gentle-swing',
'.color-shift', '.border-pulse', '.scale-breath',
'.shimmer-text', '.shimmer-slow',
'.bg-animate', '.bg-animate-fast'
].join(',');
function showSlide(n, dir) {
if (transitioning) return;
transitioning = true;
const old = slides[current];
const idx = (n + total) % total;
const nu = slides[idx];
if (idx === current) { transitioning = false; return; }
old.classList.remove('active');
let transClass;
if (dir === 1) transClass = TRANSITIONS[idx % TRANSITIONS.length];
else if (dir === -1) transClass = TRANSITIONS[(total - idx) % TRANSITIONS.length];
else transClass = nu.getAttribute('data-trans') || 'trans-fade';
transClass = 'trans-' + transClass.replace(/^trans-/, '');
nu.classList.add(transClass);
nu.style.display = 'flex';
requestAnimationFrame(() => {
requestAnimationFrame(() => {
nu.classList.add('active');
setTimeout(() => applyAnimations(nu), 50);
setTimeout(() => {
nu.classList.remove(transClass);
old.style.display = 'none';
current = idx;
updateUI();
transitioning = false;
}, 650);
});
});
}
function applyAnimations(slide) {
const staggerEls = slide.querySelectorAll('.anim-stagger');
staggerEls.forEach((el, i) => {
el.style.animation = 'none';
el.offsetHeight;
const d = parseFloat(el.getAttribute('data-d') || 0);
const delay = d * 500 + i * 45;
el.style.animation = `fadeInStagger .5s cubic-bezier(.25,.46,.45,.94) ${delay}ms both`;
});
slide.querySelectorAll(CONTINUOUS_ANIM_CLASSES).forEach(el => {
el.style.animation = 'none';
el.offsetHeight;
el.style.animation = '';
});
}
function nextSlide() { showSlide(current + 1, 1); }
function prevSlide() { showSlide(current - 1, -1); }
document.addEventListener('keydown', e => {
if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ')
{ e.preventDefault(); nextSlide(); }
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp')
{ e.preventDefault(); prevSlide(); }
if (e.key === 'Home') { e.preventDefault(); showSlide(0, 0); }
if (e.key === 'End') { e.preventDefault(); showSlide(total - 1, 0); }
});
document.addEventListener('wheel', e => {
if (e.deltaY > 30) { e.preventDefault(); nextSlide(); }
if (e.deltaY < -30) { e.preventDefault(); prevSlide(); }
}, { passive: false });
let touchStartY = 0;
document.addEventListener('touchstart', e => { touchStartY = e.touches[0].clientY; });
document.addEventListener('touchend', e => {
const diff = touchStartY - e.changedTouches[0].clientY;
if (Math.abs(diff) > 50) { diff > 0 ? nextSlide() : prevSlide(); }
});
window.addEventListener('load', () => {
setTimeout(() => applyAnimations(slides[0]), 100);
});
III. HTML Usage
Slide Markup Template
<div class="slide active" data-slide="0" data-trans="blur">
<h1 class="anim-stagger" data-d="0">Title</h1>
<p class="anim-stagger" data-d="0.15">Subtitle — appears 150 ms later</p>
<div class="anim-stagger" data-d="0.3">Content block — appears 300 ms later</div>
</div>
<div class="slide" data-slide="1" data-trans="right">
<h2 class="anim-stagger" data-d="0">Slide 2 heading</h2>
</div>
<div class="slide" data-slide="2" data-trans="zoom">
<div class="anim-stagger float" data-d="0">🚀</div>
</div>
data-trans Values
| Value | Effect | Transform |
|---|
left | Slide in from left | translateX(-100%) → 0 |
right | Slide in from right | translateX(100%) → 0 |
up | Slide in from top | translateY(-100%) → 0 |
down | Slide in from bottom | translateY(100%) → 0 |
zoom | Zoom fade-in | scale(0.7) → scale(1) |
scale-e | Elastic zoom | scale(0.3) → scale(1) |
flip | 3-D Y-axis flip | rotateY(90deg) → rotateY(0) |
fade | Pure fade | opacity 0 → 1 |
blur | Blur fade-in | blur(12px)+opacity 0 → normal |
data-d Delay Reference
data-d="0" → immediate (within 50 ms of transition start)
data-d="0.1" → ~100 ms delay
data-d="0.3" → ~200 ms delay (typical body text)
data-d="0.5" → ~300 ms delay (decorative / trailing elements)
Formula: actual delay (ms) = data-d × 500 + element index × 45
IV. No-Double-Animation: Deep Dive
The Problem
If element entrance animations fire after the slide transition ends, users see:
- Slide animates in (600 ms)
- Brief visual pause
- Elements animate in again
This two-phase motion feels disjointed and amateurish.
The Solution: Temporal Overlap
showSlide() call sequence:
requestAnimationFrame × 2
└── nu.classList.add('active') ← slide transition starts
setTimeout(applyAnimations, 50) ← elements start 50ms later (OVERLAPPING)
setTimeout(cleanup, 650) ← cleanup after transition
[CSS transition: 600ms] ████████████████████████████████
[Elements offset: 50ms] ░░░░░░░░░░░░░░░░░░░░░░░░░░░░
↑50ms
Key points:
animation-fill-mode: both keeps elements in their from (invisible) state before the animation fires — no content flash
- Element animations (500 ms) plus maximum stagger (~350 ms) finish comfortably within the 650 ms window
el.style.animation = 'none' + el.offsetHeight (forced reflow) guarantees every animation restarts from frame zero regardless of prior state
Resetting Continuous Animations Correctly
el.style.animation = 'none';
el.offsetHeight;
el.style.animation = '';
el.style.animation = 'none';
el.style.animation = '';
V. Built-in Demo
A fully functional 78-slide HTML presentation is bundled as a skill asset:
assets/demo.html
What it demonstrates
| Feature | Where to look |
|---|
| All 9 transition effects | Every chapter opener uses a different data-trans value |
anim-stagger + data-d timing | All content slides — elements enter in layered waves |
Continuous animations (pulse, float, shimmer-text, …) | Hero slides and icon cards |
| ECharts chart integration with transition coordination | Slides with .chart-box elements |
| Particle system on hero slides | Chapter title slides (.particles div) |
| Ripple click effect | Click anywhere on the presentation |
| Keyboard / wheel / touch navigation | Arrow keys, scroll, swipe |
How to run
Open assets/demo.html directly in any modern browser — no server required.
snow-slides-switch/
└── assets/
└── demo.html ← open this file
VI. Quick Integration Checklist