【更新日 : 】
【JavaScript】行ごとのスライドアップアニメーションを実装する
- Category:
- css / js
- Tags:
- JavaScript, Vanilla JS, Web Animations API, アニメーション, スクロール, フェードイン
行ごとにテキストがスライドアップするアニメーションをJavaScriptで実装したサンプルです。
Web Animations APIとIntersectionObserverを使っています。
See the Pen Text slides up line by line Vanilla JS (Web Animations API) by web_nak (@web_walking_nak) on CodePen.
JavaScript
//行ごとにスライドアップアニメーションさせる
const slideUpRowTexts = document.querySelectorAll('.js-slide-up-row');
if(slideUpRowTexts.length > 0) {
//スライドアップアニメーション関数
const textSlideUpAutoAnime = (target) => {
//行ごとにアニメーション
const texts = target.querySelectorAll('.js-slide-up-row__text');
//textsを配列に変換
const animations = [...texts].map((text, index) => {
//Web Animations APIでスライドアップアニメーション
return text.animate(
{
transform: 'translateY(0)',
},
{
fill: 'forwards', //アニメーション後に値を維持する
duration: 1000, //アニメーション時間
easing: 'cubic-bezier(0.33, 1, 0.68, 1)', //イージング
delay: index * 180 //行ごとの遅延時間
}
).finished;
});
//アニメーション終了後に元のテキストに戻す
Promise.all(animations).then(() => {
const baseText = document.querySelector('.js-slide-up-row__base');
if(baseText) {
target.innerHTML = baseText.innerHTML;
}
});
}
/*
■observerセットアップ
・初回ページロード時:画面全体を検知
・ロード後:rootMarginの値を変更
*/
let observer = new IntersectionObserver(entries => {
//監視対象の総数を取得
const total = entries.length - 1;
entries.forEach((entry, index) => {
const target = entry.target;
//アニメーションフラグ設定
target.isFinished = false;
//要素が画面内にある場合はアニメーション発火
if(entry.isIntersecting) {
textSlideUpAutoAnime(target);
target.isFinished = true;
}
//要素の監視を解除
observer.unobserve(target);
//一通り監視し終わったら スクロール用のobserverをセットアップ
if(total === index) {
let observerScroll = new IntersectionObserver(entries => {
entries.forEach(entry => {
const target = entry.target;
//要素が画面内にある場合はアニメーション発火
if(entry.isIntersecting) {
textSlideUpAutoAnime(target);
//要素の監視を解除
observerScroll.unobserve(target);
}
});
//検知範囲を設定
}, {rootMargin: '-20% 0px -20% 0px'});
//アニメーションが終了していない要素のみ監視対象とする
slideUpRowTexts.forEach(slideUpRowText => {
if(!slideUpRowText.isFinished) {
observerScroll.observe(slideUpRowText);
}
});
}
});
});
const setUpText = (slideUpRowText, baseText) => {
//表示領域確保用の透明テキスト
let html = '<span class="js-slide-up-row__base" aria-hidden="true">' + baseText + '</span><span class="u-visually-hidden">' + baseText + '</span>';
//表示領域全体の高さを取得
const textHeight = slideUpRowText.clientHeight;
//1行の高さを取得
const styles = getComputedStyle(slideUpRowText);
let lineHeight = styles.lineHeight;
if(lineHeight === 'normal') {
//line-heightが未設定だった場合は1行の高さをチェックする
slideUpRowText.insertAdjacentHTML('beforeend', '<span class="js-slide-up-row__checker" aria-hidden="true"> </span>');
lineHeight = slideUpRowText.querySelector('.js-slide-up-row__checker').clientHeight;
} else {
lineHeight = parseFloat(lineHeight);
}
//現在何行なのか調べる
const row = textHeight / lineHeight;
//アニメーション用 位置変更設定
let translateY = lineHeight;
//アニメーションが終了していたら位置変更しない
if(slideUpRowText.isFinished) {
translateY = 0;
}
//行ごとにclip-pathを設定する
for (let i = 0; i < row; i++) {
const insetTop = lineHeight * i;
let insetBottom = textHeight - (lineHeight * (i + 1));
if(insetBottom < 0) {
insetBottom = 0;
}
html += '<span class="js-slide-up-row__line" aria-hidden="true" style="clip-path: inset(' + insetTop + 'px 0 ' + insetBottom + 'px)"><span class="js-slide-up-row__text" style="clip-path: inset(' + insetTop + 'px 0 ' + insetBottom + 'px);transform: translateY(' + translateY + 'px)">' + baseText + '</span></span>';
}
//中身をリセット
slideUpRowText.textContent = '';
//定義したHTMLを反映
slideUpRowText.insertAdjacentHTML('beforeend', html);
//セットアップ完了classの追加
slideUpRowText.classList.add('is-setup');
}
//初回セットアップ
slideUpRowTexts.forEach(slideUpRowText => {
//ベーステキストの取得
const baseText = slideUpRowText.innerHTML;
//アニメーション用マークアップ
setUpText(slideUpRowText,baseText);
//observer初回監視開始
observer.observe(slideUpRowText);
/*
レスポンシブ対応
*/
//表示エリアリサイズ監視 ResizeObserver
const resizeObserver = new ResizeObserver(() => {
//アニメーション用マークアップ再定義
setUpText(slideUpRowText, baseText);
});
//リサイズ監視開始
resizeObserver.observe(slideUpRowText,slideUpRowText);
});
}
HTML
アニメーションさせたいテキスト要素に .js-slide-up-row
を追加します。
<p class="js-slide-up-row">
行ごとにスライドアップするテキストアニメーションです。
</p>
CSS
.js-slide-up-row {
position: relative;
opacity: 0;
width: fit-content;
}
.js-slide-up-row.is-setup {
opacity: 1;
}
.js-slide-up-row__base {
opacity: 0;
}
.js-slide-up-row__line,
.js-slide-up-row__checker {
position: absolute;
top: 0;
left: 0;
width: 100%;
pointer-events: none;
}
.js-slide-up-row__text {
display: block;
}
.u-visually-hidden {
overflow: hidden;
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}