【更新日 : 】
【CSS , JavaScript】Intersection Observerでスクロールアニメーションを実装する
- Category:
- css / js
- Tags:
- css, css3, JavaScript, Vanilla JS, アニメーション, スクロール, フェードイン, 自作関数
Intersection Observerでスクロールアニメーションを実装したサンプルです。
ブレイクポイント毎の設定変更、単一orエリアごとに発火(複数同時にもアニメーションできる)する機能があります。
See the Pen Intersection Observer Scroll Animation by web_nak (@web_walking_nak) on CodePen.
HTML
複数アニメーションする場合
- 親要素に .js-anime-box を追加。
- アニメーションさせたい内包要素に .js-anime-elm を追加。
単一アニメーションの場合
- 要素に .js-anime-box と .js-anime-elm を追加。
<div class="js-anime-box">
<p class="js-anime-elm">エリア内複数アニメーション要素1</p>
<p class="js-anime-elm">エリア内複数アニメーション要素2</p>
</div>
<p class="js-anime-box js-anime-elm">単一アニメーション要素</p>
CSS
.js-anime-elm
に .is-anime
が追加されると発火します。
サイトに合わせて適宜調整をしてください。
.js-anime-elm {
opacity: 0;
animation-fill-mode: both;
animation-duration: 1.2s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.is-anime {
animation-name: fadeIn;
}
.is-anime.is-anime-fadeInUp {
animation-name: fadeInUp;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translate3d(0,100px,0);
}
100% {
opacity: 1;
transform: translate3d(0,0,0);
}
}
JavaScript
//Intersection Observerスクロールアニメーション
function observerAnime(setOptions) {
//options
const defaultOptions = {
root: null, //IntersectionObserverのroot
rootMargin: '0px', //IntersectionObserverのrootMargin
threshold: 0, //IntersectionObserverのthreshold
loop: false, //アニメーション繰り返し設定
responsive: null, //レスポンシブ設定
/*
//レスポンシブ設定
responsive: [{
breakpoint: 1024, //ブレイクポイント max-width
rootMargin: '0px',
threshold: 0,
loop: false
}, {
breakpoint: 500,
rootMargin: '0px',
threshold: 0,
loop: false
}]
*/
targetAreaClass : '.js-anime-box', //ターゲットを内包するエリアclass
targetClass: '.js-anime-elm', //ターゲットclass
animeSetClass: 'is-anime', //アニメーション実行時に追加するclass
}
//設定をマージ
const options = Object.assign({}, defaultOptions, setOptions);
//ターゲット要素取得
let targets = document.querySelectorAll(options.targetAreaClass);
//要素が存在するかチェック
if(targets.length === 0) {
return false;
}
//各設定を取得
const targetClass = options.targetClass;
const animeSetClass = options.animeSetClass;
options.loopFlg = options.loop;
//observer初期設定
let observer;
setObserver(options);
//breakpoint切り替え監視
let breakpoint = getBreakpoint(options);
//breakpointが設定されていたら処理する
if(breakpoint !== null) {
let nowBreakpoint = breakpoint;
window.addEventListener('resize', function() {
//ブレイクポイントが切り替わったタイミンでobserverをリセット・再定義
breakpoint = getBreakpoint(options);
if(nowBreakpoint !== breakpoint) {
nowBreakpoint = breakpoint;
//observerリセット
targets.forEach(function(target){
observer.unobserve(target);
});
//observer再定義
setObserver(options);
}
});
}
//observer定義関数
function setObserver(options) {
//ブレイクポイントに応じたオプションを取得
const getOptions = getOptionsFunc(options);
options.loopFlg = getOptions[1];
//observer設定
observer = new IntersectionObserver(setAnimeClass, getOptions[0]);
targets.forEach(function(target){
observer.observe(target);
});
}
//アニメーション処理
function setAnimeClass(entries, observer) {
//ループフラグ取得
const loopFlg = options.loopFlg;
entries.forEach(entry => {
//ターゲット要素取得
const target = entry.target;
//ターゲット要素の子要素取得
const children = target.querySelectorAll(targetClass);
//要素が画面に入ると処理
if(entry.isIntersecting) {
//子要素がある場合は子要素にclassを追加
if(children.length !== 0) {
children.forEach(function(child){
child.classList.add(animeSetClass);
});
//子要素がない場合はターゲット自身にclassを追加
} else {
target.classList.add(animeSetClass);
}
//ループなしの場合
if(!loopFlg) {
//要素の監視を解除
observer.unobserve(target);
}
//ループありの場合
} else if(loopFlg) {
//子要素がある場合は子要素に設定されたclassを削除
if(children.length !== 0) {
children.forEach(function(child){
child.classList.remove(animeSetClass);
});
//子要素がない場合はターゲット自身のclassを削除
} else {
target.classList.remove(animeSetClass);
}
}
});
}
//ブレイクポイントごとの設定取得関数
function getBreakpoint(options) {
let responsive = options.responsive;
//ブレイクポイントなしの場合は処理しない
if(!responsive) {
return responsive;
}
//ウィンドウの幅取得
const winWidth = window.innerWidth;
//ウィンドウの幅とブレイクポイントを比較・取得
let reverse = responsive.concat().reverse();
let breakpoint = 'full';
for (let i = 0; i < reverse.length; i++) {
if(winWidth <= reverse[i].breakpoint) {
breakpoint = reverse[i].breakpoint;
break;
}
}
return breakpoint;
}
//オプション取得関数
function getOptionsFunc(options) {
//ベース設定取得
let root = options.root;
let rootMargin = options.rootMargin;
let threshold = options.threshold;
let loop = options.loop;
//レスポンシブ切り替え
const responsive = options.responsive;
//ブレイクポイントありの場合は設定を取得
if(responsive) {
//ウィンドウの幅取得
const winWidth = window.innerWidth;
//ウィンドウの幅とブレイクポイントを比較・各設定取得
for (let i = 0; i < responsive.length; i++) {
if(winWidth <= responsive[i].breakpoint) {
root = responsive[i].root;
rootMargin = responsive[i].rootMargin;
threshold = responsive[i].threshold;
loop = responsive[i].loop;
}
}
}
//IntersectionObserverのオプションとループフラグを返す
return [
{
root: root,
rootMargin: rootMargin,
threshold: threshold
},
loop
];
}
}
関数の呼び出し
任意の場所で以下を実行し関数を呼び出してください。
loop設定を有効化する場合、rootMarginは’0px’設定が良さそうです。
//Intersection Observerスクロールアニメーション関数呼び出し
observerAnime({
rootMargin: '-50%', //画面の中央で発火。IntersectionObserverのrootMargin デフォルトは'0px'
loop: false, //アニメーション繰り返し設定 デフォルトはfalse
threshold: 0, //threshold デフォルトは0
responsive: [{
breakpoint: 1024, //ブレイクポイント max-width
rootMargin: '-30%',
}, {
breakpoint: 400,
rootMargin: '0px',
loop: true, //trueにする場合、rootMarginは'0px'が良さそうです
}]
});
関数のオプション
root: null, //IntersectionObserverのroot
rootMargin: '0px', //IntersectionObserverのrootMargin
threshold: 0, //IntersectionObserverのthreshold
loop: false, //アニメーション繰り返し設定
responsive: null, //レスポンシブ設定
/*
//レスポンシブ設定
responsive: [{
breakpoint: 1024, //ブレイクポイント max-width
rootMargin: '0px',
threshold: 0,
loop: false
}, {
breakpoint: 500,
rootMargin: '0px',
threshold: 0,
loop: false
}]
*/
targetAreaClass : '.js-anime-box', //ターゲットを内包するエリアclass
targetClass: '.js-anime-elm', //ターゲットclass
animeSetClass: 'is-anime', //アニメーション実行時に追加するclass
ブレイクポイントなしのシンプル版
ブレイクポイント切り替えのないシンプル版も作ってみました。
レスポンシブ設定がない以外はすべて同じ設定です。
See the Pen Intersection Observer Scroll Animation 【simple】 by web_nak (@web_walking_nak) on CodePen.
JavaScript
//【シンプル版】Intersection Observerスクロールアニメーション
function observerAnime() {
//options
const options = {
root: null, //IntersectionObserverのroot
rootMargin: '-50%', //IntersectionObserverのrootMargin
threshold: 0, //IntersectionObserverのthreshold
loop: false, //アニメーション繰り返し設定
targetAreaClass : '.js-anime-box', //ターゲットを内包するエリアclass
targetClass: '.js-anime-elm', //ターゲットclass
animeSetClass: 'is-anime', //アニメーション実行時に追加するclass
}
//ターゲット要素取得
let targets = document.querySelectorAll(options.targetAreaClass);
//要素が存在するかチェック
if(targets.length === 0) {
return false;
}
//各設定を取得
const targetClass = options.targetClass;
const animeSetClass = options.animeSetClass;
options.loopFlg = options.loop;
//observer初期設定
let observer;
setObserver(options);
//observer定義関数
function setObserver(options) {
//オプションを取得
options.loopFlg = options.loop;
//observer設定
observer = new IntersectionObserver(setAnimeClass, {
root: options.root,
rootMargin: options.rootMargin,
threshold: options.threshold
});
targets.forEach(function(target){
observer.observe(target);
});
}
//アニメーション処理
function setAnimeClass(entries, observer) {
//ループフラグ取得
const loopFlg = options.loopFlg;
entries.forEach(entry => {
//ターゲット要素取得
const target = entry.target;
//ターゲット要素の子要素取得
const children = target.querySelectorAll(targetClass);
//要素が画面に入ると処理
if(entry.isIntersecting) {
//子要素がある場合は子要素にclassを追加
if(children.length !== 0) {
children.forEach(function(child){
child.classList.add(animeSetClass);
});
//子要素がない場合はターゲット自身にclassを追加
} else {
target.classList.add(animeSetClass);
}
//ループなしの場合
if(!loopFlg) {
//要素の監視を解除
observer.unobserve(target);
}
//ループありの場合
} else if(loopFlg) {
//子要素がある場合は子要素に設定されたclassを削除
if(children.length !== 0) {
children.forEach(function(child){
child.classList.remove(animeSetClass);
});
//子要素がない場合はターゲット自身のclassを削除
} else {
target.classList.remove(animeSetClass);
}
}
});
}
}
関数の呼び出し
任意の場所で以下を実行し関数を呼び出してください。
//関数呼び出し
observerAnime();