【更新日 : 】
【脱Jquery】javascriptでスライド系アニメーション(slideDown、slideUp、slideToggle)を実装する
- Category:
- JavaScript
Vanilla JSでスライド系アニメーション(slideDown、slideUp、slideToggle)を実装したサンプルです。
イージング編集と、コールバック関数に対応させました。
後から作成したWeb Animations API 版の方がより最適化してあります。
より少ないコードで実装できる
CSS GridとVanilla JSでスライド系アニメーション(slideToggle)を実装したサンプルを公開しました。
Web Animations API 版
See the Pen slideDown slideUp slideToggle Vanilla JS (Web Animations API) by web_nak (@web_walking_nak) on CodePen.
JavaScript
/**
* Web Animations APIによるslideUp・slideDown・slideToggleアニメーション処理関数
* @type {Object}
* @param {HTMLElement} target ターゲット設定(必須)
* @param {string} animeType アニメーションの種類 'slideUp' or 'slideDown' or 'slideToggle'(任意 デフォルト:'slideToggle')
* @param {number} duration アニメーション時間 ミリ秒(任意 デフォルト:400)
* @param {string} easing 'Web Animations API'で設定できるイージング(任意 デフォルト:ease)
* @param {string} displayStyle 'fadeIn'表示後に付与されるcss displayの値(任意 デフォルト:block)
* @param {function} callBack アニメーション後に呼び出すコールバック関数(任意 デフォルト:null)
*/
const slideAnime = async setOptions => {
'use strict';
const typeSlideDown = 'slideDown';
const typeSlideUp = 'slideUp';
const typeSlideToggle = 'slideToggle';
//デフォルト設定
const defaultOptions = {
target: false,
animeType: typeSlideToggle,
duration: 400,
easing: 'ease',
displayStyle: 'block',
callBack: null,
}
//設定をマージ
const options = Object.assign({}, defaultOptions, setOptions);
//要素が存在しない場合は処理を終了
const target = options.target;
if(!target) {
return;
}
//slideToggle分岐
let animeType = options.animeType;
const styles = getComputedStyle(target);
const textNone = 'none';
const isDisplayNone = styles.display === textNone;
if (animeType === typeSlideToggle) {
animeType = isDisplayNone ? typeSlideDown : typeSlideUp;
}
const busyClass = 'is-slide-busy';
const targetClassList = target.classList;
//実行判別
const isSlideDown = animeType === typeSlideDown;
const isSlideUp = animeType === typeSlideUp;
const isBusy = targetClassList.contains(busyClass);
if (
//slideUp 既に非表示 or 実行中だった場合は処理しない
(isSlideUp && (isDisplayNone || isBusy))
//slideDown 既に表示されている or 実行中だった場合は処理しない
|| (isSlideDown && (!isDisplayNone || isBusy))
//有効なキーワードではない場合も処理しない
|| (!isSlideUp && !isSlideDown)
) {
return;
}
//実行中class追加
targetClassList.add(busyClass);
//初期設定
const targetStyle = target.style;
targetStyle.overflow = 'hidden';
const displayStyle = options.displayStyle;
//初期設定 アニメーション分岐
if(isSlideDown) {
//'slideDown'初期設定 要素を表示
targetStyle.display = displayStyle;
}
//高さ関連のstyleを取得
const heightVal = {
height: target.getBoundingClientRect().height + 'px',
marginTop: styles.marginTop,
marginBottom: styles.marginBottom,
paddingTop: styles.paddingTop,
paddingBottom: styles.paddingBottom
};
const styleKeys = Object.keys(heightVal);
//有効なstyleのみ取り出し
styleKeys.forEach(key => {
//値が0の場合は削除する
if(parseFloat(heightVal[key]) === 0) {
delete heightVal[key];
}
});
//有効なプロパティが1件もない場合は処理を終了
if(styleKeys.length === 0) {
return;
}
//スライドアニメーション
if(isSlideDown) {
//style 0 → 設定値へ アニメーション
styleKeys.forEach(key => {
targetStyle[key] = 0;
});
} else {
//style 設定値 → 0 へアニメーション
styleKeys.forEach(key => {
targetStyle[key] = heightVal[key];
heightVal[key] = 0;
});
}
await target.animate(
heightVal,
{
duration: options.duration,
easing: options.easing
}
).finished;
//アニメーション終了処理
//実行中class削除
targetClassList.remove(busyClass);
//アニメーション用styleを削除
targetStyle.overflow = '';
styleKeys.forEach(key => {
targetStyle[key] = '';
});
//'slideUp'の場合は非表示
if(isSlideUp) {
targetStyle.display = textNone;
}
//コールバック関数が設定されていたら呼び出す
const callBack = options.callBack;
if(typeof callBack === 'function') {
callBack();
}
}
関数の呼び出し
任意の場所で以下を実行し関数を呼び出してください。
ターゲット以外は任意設定です。
slideAnime({
//ターゲット設定(必須)
target: target,
//↓は任意設定
//アニメーションの種類 'slideUp' or 'slideDown' or 'slideToggle'(任意 デフォルト:'slideToggle')
animeType: 'slideToggle',
//アニメーション時間 ミリ秒(任意 デフォルト:400)
duration: 400,
//Web Animations APIで設定できるイージング(任意 デフォルト:ease)
easing: 'ease',
//fadeIn表示後に付与されるcss displayの値(任意 デフォルト:block)
displayStyle: 'block',
//アニメーション後に呼び出すコールバック関数(任意 デフォルト:null)
callBack: () => {
console.log('slideアニメ終了');
}
});
requestAnimationFrame() 版
See the Pen slideDown slideUp slideToggle Vanilla JS (Easing support) by web_nak (@web_walking_nak) on CodePen.
JavaScript
/*
■slideUp・slideDown・slideToggleアニメーション処理関数
animeType: 'slideUp' or 'slideDown' or 'slideToggle'
elm: 対象要素
duration: アニメーション時間
[callBack: コールバック関数(任意)]
*/
function slideAnime(animeType, elm, duration, callBack) {
'use strict';
//slideToggle分岐
if (animeType === 'slideToggle') {
animeType = getComputedStyle(elm).display === 'none' ? 'slideDown' : 'slideUp';
}
//実行判別
if (
//slideUp 既に非表示 or 実行中だった場合は処理しない
(animeType === 'slideUp' && (getComputedStyle(elm).display === 'none' || elm.classList.contains('is-slide-busy')))
//slideDown 既に表示されている or 実行中だった場合は処理しない
|| (animeType === 'slideDown' && (getComputedStyle(elm).display !== 'none' || elm.classList.contains('is-slide-busy')))
//有効なキーワードではない場合も処理しない
|| (animeType !== 'slideUp' && animeType !== 'slideDown')
) {
return false;
}
//実行中class追加
elm.classList.add('is-slide-busy');
/*
イージング設定
・参考サイト
https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
【JavaScript】スムーススクロールをjQueryを使わずにシンプルに実装する【Vanilla JS】
「t:アニメーションの経過時間」「b:始点」「c:変化量」「d:変化にかける時間」
*/
function easing(t, b, c, d) {
return c * (0.5 - Math.cos((t / d) * Math.PI) / 2) + b;
}
//初期設定
elm.style.overflow = 'hidden';
//初期設定 アニメーション分岐
let slideFunc;
if(animeType === 'slideDown') {
//初期設定 要素を表示
elm.style.display = 'block';
//アニメーション関数設定
slideFunc = function(elapsedTime,duration,heightVal) {
Object.keys(heightVal).forEach(function(key) {
//easing(「アニメーションの経過時間」,「始点」,「変化量」,「変化にかける時間」)
elm.style[key] = easing(elapsedTime, 0, heightVal[key], duration) + 'px';
});
}
} else if(animeType === 'slideUp') {
//アニメーション関数設定
slideFunc = function(elapsedTime,duration,heightVal) {
Object.keys(heightVal).forEach(function(key) {
elm.style[key] = (heightVal[key] - easing(elapsedTime, 0, heightVal[key], duration)) + 'px';
});
}
}
//高さ関連のstyleを取得
const styles = getComputedStyle(elm);
const heightVal = {
height: elm.getBoundingClientRect().height,
marginTop: parseFloat(styles.marginTop),
marginBottom: parseFloat(styles.marginBottom),
paddingTop: parseFloat(styles.paddingTop),
paddingBottom: parseFloat(styles.paddingBottom)
};
//有効なstyleのみ取り出し
Object.keys(heightVal).forEach(function(key) {
//値が0の場合は削除する
if(heightVal[key] === 0) {
delete heightVal[key];
}
});
//有効なプロパティが1件もない場合は処理を終了
if(Object.keys(heightVal).length === 0) {
return false;
}
//アニメーション開始時間
const start = new Date();
function mainAnime() {
//イベント発生後の経過時間
let elapsedTime = new Date() - start;
//アニメーション終了処理
if (elapsedTime > duration) {
//実行中class削除
elm.classList.remove('is-slide-busy');
//slideUpの場合は要素を非表示
if(animeType === 'slideUp') {
elm.style.display = 'none';
}
//アニメーション用styleを削除
elm.style.overflow = '';
Object.keys(heightVal).forEach(function(key) {
elm.style[key] = '';
});
//コールバック関数が設定されていたら呼び出す
if(typeof callBack === 'function') {
callBack();
}
//処理を終了
return false;
}
//アニメーション実行処理
slideFunc(elapsedTime,duration,heightVal);
requestAnimationFrame(mainAnime);
}
//アニメーション初回呼び出し
requestAnimationFrame(mainAnime);
}
関数の呼び出し
任意の場所で以下を実行し関数を呼び出してください。
//slideDown
slideAnime('slideDown', 対象要素, アニメーション時間 [, コールバック関数(任意)]);
//slideUp
slideAnime('slideUp', 対象要素, アニメーション時間 [, コールバック関数(任意)]);
//slideToggle
slideAnime('slideToggle', 対象要素, アニメーション時間 [, コールバック関数(任意)]);
動作サンプル
HTML
<button id="js-slide-down">fadeIn</button>
<div class="js-slide-down-elm">slideDown要素</div>
<button id="js-slide-up">slideUp</button>
<div class="js-slide-up-elm">slideUp要素</div>
<button id="js-slide-toggle">slideToggle</button>
<div class="js-slide-toggle-elm">slideToggle要素</div>
CSS
/* slideDown用設定 */
.js-slide-down-elm {
display: none;
}
JavaScript
イージング設定や処理速度、コールバック関数(任意)はサイトに合わせて編集してください。
//【Web Animations API 版】
//slideDownテスト
const slideDownElm = document.querySelector('.js-slide-down-elm');
document.getElementById('js-slide-down').addEventListener('click', () => {
//slideDown呼び出し
slideAnime({
target: slideDownElm,
animeType: 'slideDown',
duration: 400,
easing: 'ease',
displayStyle: 'block',
callBack: () => {
console.log('slideDown終了');
}
});
});
//slideUpテスト
const slideUpElm = document.querySelector('.js-slide-up-elm');
document.getElementById('js-slide-up').addEventListener('click', () => {
//slideUp呼び出し
slideAnime({
target: slideUpElm,
animeType: 'slideUp',
callBack: () => {
console.log('slideUp終了');
}
});
});
//slideToggleテスト
const slideToggleElm = document.querySelector('.js-slide-toggle-elm');
document.getElementById('js-slide-toggle').addEventListener('click', () => {
//slideToggle呼び出し
slideAnime({
target: slideToggleElm,
animeType: 'slideToggle',
callBack: () => {
console.log('slideToggle終了');
}
});
});
//【requestAnimationFrame() 版】
//slideDownテスト
const slideDownElm = document.querySelector('.js-slide-down-elm');
document.getElementById('js-slide-down').addEventListener('click', function() {
/*
・slideDown呼び出し
slideAnime('slideDown', 対象要素, アニメーション時間 [, コールバック関数(任意)]);
*/
slideAnime('slideDown', slideDownElm, 400, function() {
console.log('slideDown終了');
});
});
//slideUpテスト
const slideUpElm = document.querySelector('.js-slide-up-elm');
document.getElementById('js-slide-up').addEventListener('click', function() {
/*
・slideUp呼び出し
slideAnime('slideUp', 対象要素, アニメーション時間 [, コールバック関数(任意)]);
*/
slideAnime('slideUp', slideUpElm, 400, function() {
console.log('slideUp終了');
});
});
//slideToggleテスト
const slideToggleElm = document.querySelector('.js-slide-toggle-elm');
document.getElementById('js-slide-toggle').addEventListener('click', function() {
/*
・slideToggle呼び出し
slideAnime('slideToggle', 対象要素, アニメーション時間 [, コールバック関数(任意)]);
*/
slideAnime('slideToggle', slideToggleElm, 400, function() {
console.log('slideToggle終了');
});
});