Web production note

 【更新日 :

【脱Jquery】javascriptでスライド系アニメーション(slideDown、slideUp、slideToggle)を実装する

Category:
JavaScript

Vanilla JSでスライド系アニメーション(slideDown、slideUp、slideToggle)を実装したサンプルです。
イージング編集と、コールバック関数に対応させました。
後から作成したWeb Animations API 版の方がより最適化してあります。

より少ないコードで実装できる
CSS GridとVanilla JSでスライド系アニメーション(slideToggle)を実装したサンプルを公開しました。

Web Animations API 版

codepen

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() 版

codepen

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終了');
  });
});