Result
Tailwindの設計をヒントにして作ったスクロール連動アニメーション。
少しのJavaScriptとCSSで実装可能、アニメーションもカスタマイズ可能と、軽量で使いやすいのがメリットとなっています。
上記サンプルはブロックごとにクリックで遷移しますがコードはスクロール連動となっています。
CDNも用意されていますので気軽にテスト、導入できます。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Michal-Skoula/simple-scroll-animations@master/release/latest/styles.css"> <script defer src="https://cdn.jsdelivr.net/gh/Michal-Skoula/simple-scroll-animations@master/release/latest/script.js"></script>
css
:root { --reanimate-on-scroll-by-default: 0; --default-animation-duration: 500ms; --staggered-step-amount:250ms; --delay-step-amount: 400ms; --blur-strength: 3px; --translate-amount-x: 40%; --translate-amount-y: 40%; } @media(prefers-reduced-motion) { .animate.show { transition: none !important; } } .animate { opacity: 0; filter: blur(var(--blur-strength)); transition: transform var(--default-animation-duration) ease-in-out, opacity var(--default-animation-duration), filter calc(var(--default-animation-duration) * 2/3); } .animate.show, .animate.show.fade-in { opacity: 1; filter: blur(0); } .animate.fade-in { opacity: 0; filter: blur(var(--blur-strength)); } .noblur { filter: blur(0) !important; } .animate.bottom-to-top { transform: translateY(var(--translate-amount-y)); } .animate.show.bottom-to-top { transform: translateY(0); } .animate.right-to-left { transform: translateX(var(--translate-amount-x)); } .animate.show.right-to-left { transform: translateX(0); } .animate.top-to-bottom { transform: translateY(calc(var(--translate-amount-y) * -1)); } .animate.show.top-to-bottom { transform: translateY(0); } .animate.left-to-right { transform: translateX(calc(var(--translate-amount-x) * -1)); } .animate.show.left-to-right { transform: translateX(0); } .animate.staggered { transition-delay: calc(var(--delay-step-amount) * var(--index)); } .animate.delay100, .animate.delay200, .animate.delay300, .animate.delay400, .animate.delay500, .animate.delay600, .animate.delay700, .animate.delay800, .animate.delay900, .animate.delay1000 { transition-delay: calc(var(--delay-step-amount) * var(--delay)); } .animate.duration100, .animate.duration200, .animate.duration300, .animate.duration400, .animate.duration500, .animate.duration600, .animate.duration700, .animate.duration800, .animate.duration900, .animate.duration1000 { transition-duration: calc(var(--staggered-step-amount) * var(--duration)); } .animate.staggered:nth-child(2) { --index: 1; } .animate.staggered:nth-child(3) { --index: 2; } .animate.staggered:nth-child(4) { --index: 3; } .animate.staggered:nth-child(5) { --index: 4; } .animate.staggered:nth-child(6) { --index: 5; } .animate.staggered:nth-child(7) { --index: 6; } .animate.staggered:nth-child(8) { --index: 7; } .animate.staggered:nth-child(9) { --index: 8; } .animate.staggered:nth-child(10) { --index: 9; } .animate.delay100 { --delay: 1; } .animate.delay200 { --delay: 2; } .animate.delay300 { --delay: 3; } .animate.delay400 { --delay: 4; } .animate.delay500 { --delay: 5; } .animate.delay600 { --delay: 6; } .animate.delay700 { --delay: 7; } .animate.delay800 { --delay: 8; } .animate.delay900 { --delay: 9; } .animate.delay1000 { --delay: 10; } .animate.duration100 { --duration: 1; } .animate.duration200 { --duration: 2; } .animate.duration300 { --duration: 3; } .animate.duration400 { --duration: 4; } .animate.duration500 { --duration: 5; } .animate.duration600 { --duration: 6; } .animate.duration700 { --duration: 7; } .animate.duration800 { --duration: 8; } .animate.duration900 { --duration: 9; } .animate.duration1000 { --duration: 10; }
Tailwind互換というわけではありませんが同じような感覚で使えると思います。
javascript
const rootElement = document.documentElement; const reanimate = getComputedStyle(rootElement).getPropertyValue('--reanimate-on-scroll-by-default'); reanimate.trim(); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if(entry.isIntersecting) { entry.target.classList.add('show') } else if(reanimate == 1 && !entry.target.classList.contains('once')) { entry.target.classList.remove('show') } else if(reanimate == 0 && entry.target.classList.contains('always')) { entry.target.classList.remove('show') } else if(reanimate == 0) {} }); }); const toBeAnimated = document.querySelectorAll('.animate'); toBeAnimated.forEach((element) => {observer.observe(element)});
Intersection Observer APIを利用して、要素がviewportに入ったか、或いは出たかを監視、それに応じてCSSクラスとして.show
を追加・削除することで実現します。よくあるパターンですね。
アニメーションはCSSで制御しているのでCSSの知識があれば様々な表現が可能ですね。