Edited at

jQueryを使わずに要素のアニメーションをしてみる

More than 1 year has passed since last update.


はじめに

jQueryのアニメーション .animate() はとても便利ですが、プラグインなしでは色が変わるアニメーションを作れなかったり transform 関係のアニメーションが若干面倒です。

そして、CSS3からはJavaScriptなしでもアニメーションができるようになりましたが、CSSを別に書かなければならないのでJavaScriptと連携させる時に面倒だったりするのも事実。

それならJavaScriptで動的にCSSアニメーションをしてみようという感じです。

※あくまでも参考用のコードなので、何か予期せぬ動作を起こしたりするかもしれません。


transition を利用したJavaScriptのアニメーション

単純なものならこんな感じでしょうか。

jQueryの .animate() に似た表記で書けるように。


transitionアニメーション

// アニメーション用関数

function transition(selector, style, option, callback) {
var
// 要素の取得
elements = document.querySelectorAll(selector),

// アニメーションさせるプロパティの配列
properties = Object.keys(style),

// element.style.transition 用の変数
transitionValue;

// element.style.transition 用の値に整える
if (!option) {
// 何も指定されてない場合
transitionValue = 'all 400ms ease';
} else if (typeof option == 'string') {
// transition の値そのままの場合
transitionValue = option;
} else if (typeof option == 'number') {
// 数値で duration が指定された場合
transitionValue = 'all ' + option + 'ms ease';
} else {
// オブジェクトで渡されたとき
transitionValue =
(option.property || 'all') + ' ' +
(option.duration || 400) + 'ms ' +
(option.easing || 'ease') + ' ' +
(option.delay || 0) + 'ms';
}

// 各要素に適用させる
Array.prototype.forEach.call(elements, function (element) {
var
// 要素のスタイル
elemStyle = window.getComputedStyle(element),

// タイマー
timer = null,
delay,

// アニメーション終了後の処理
transitionend = function () {
clearTimeout(timer);

element.removeEventListener('transitionend', transitionend);
element.style.transition = '';

// コールバック関数がある場合は実行
if (typeof callback == 'function') {
callback.call(element);
}
};

// 要素に transition を適用する
element.style.transition = transitionValue;

// transitionend でアニメーションの終わりに実行させる
element.addEventListener('transitionend', transitionend);

// transitionend が発火しなかった時の保険
delay = (parseFloat(elemStyle.transitionDuration) + parseFloat(elemStyle.transitionDelay)) * 1000 + 50;
timer = setTimeout(transitionend, delay);

// 要素にスタイルを適用(チェインケースかキャメルケースかで分ける)
properties.forEach(function (property) {
if (property.indexOf('-') > -1) {
element.style.setProperty(property, style[property]);
} else {
element.style[property] = style[property];
}
});
});
}


使い方としてはこんな感じです。


使用例

/* .moveBox が1秒かけて右下に 200px, 200px 動くアニメーション */

// パターン1
transition('.moveBox', {
transform: 'translate(200px, 200px)',
}, 'all 1s ease');

// パターン2
transition('.moveBox', {
transform: 'translate(200px, 200px)',
}, 1000);

// パターン3
transition('.moveBox', {
transform: 'translate(200px, 200px)',
}, {
duration: 1000,
});

/* .fadeBox が1秒後に600ミリ秒かけて180°回転しながらフェードアウトした後にコンソールに出力 */
// パターン1
transition('.fadeBox', {
opacity: '0',
transform: 'rotate(180deg)',
}, 'all 600ms ease-in-out 1s', function () {
console.log('終わったよ');
});

// パターン2
transition('.fadeBox', {
opacity: '0',
transform: 'rotate(180deg)',
}, {
duration: 600,
easing: 'ease-in-out',
delay: 1000,
}, function () {
console.log('終わったよ');
});

/* jQueryの標準ではできない色の変化アニメーション */
// パターン1
transition('.colorBox', {
backgroundColor: '#f00',
});

// パターン2
transition('.colorBox', {
'background-color': '#f00',
});


コールバックは、取得した要素の数だけ実行されます。

またコールバック関数の this にはそれぞれの要素が入っています。jQueryと似たような感じです。


animation@keyframes を利用したJavaScriptのアニメーション

transition では直線的なアニメーションしかできませんが、animation@keyframes で複雑なアニメーションを作ることができます。

これをJavaScriptで動的に指定できたらいいですよね。


keyframesアニメーション

(function () {

// @keyframes用のstyle要素の生成
var styleSheet = document.head.appendChild(document.createElement('style')).sheet;

// keyframes の名前用ランダムキー
function randomKey() {
return 'keyframes' + Math.random().toString().replace('.', '');
}

// CSSの @keyframes ルールの削除
function resetAnimation(key) {
var
rules = styleSheet.cssRules,
length = rules.length,
i = 0;

for (; i < length; i++) {
if (rules[i].name === key) {
styleSheet.deleteRule(i);
break;
}
}
}

// 本体
window.keyframes = function (selector, keys, option, callback) {
var
// 要素の取得
elements = document.querySelectorAll(selector),

// @keyframes 用の変数
keyframesRulue = '',

// アニメーションに使用するプロパティの配列
fillStyle = [],

// element.style.animation 用の変数
animationValue;

// @keyframes 用に整える
Object.keys(keys).forEach(function (per) {
var
style = keys[per],
styleArray = Object.keys(style),
cssText = '';

keyframesRulue += per + '{';

styleArray.forEach(function (property) {
cssText += property + ':' + style[property] + ';';
});

keyframesRulue += cssText + '}';

fillStyle.push.apply(fillStyle, styleArray);
});

// 重複しているプロパティを削除
fillStyle = fillStyle.filter(function (value, i) {
return fillStyle.indexOf(value) === i;
});

// element.style.animation 用に整える
if (!option) {
// 何も指定されてない場合
animationValue = '400ms ease 0s 1 normal none';
} else if (typeof option == 'string') {
// テキストの場合
animationValue = option;
} else if (typeof option == 'number') {
// 数値で duration が指定された場合
animationValue = option + 'ms ease 0s 1 normal none';
} else {
// オブジェクトで渡されたとき
animationValue =
(option.duration || 400) + 'ms ' +
(option.easing || 'ease') + ' ' +
(option.delay || 0) + 'ms ' +
(option.count || 1) + ' ' +
(option.direction || 'normal') + ' ' +
(option.fill || 'none');
}

// 各要素に適用させる
fillStyle.forEach.call(elements, function (element) {
var
// @keyframes 用の名前の生成
key = randomKey(),

// 要素のスタイルオブジェクトの取得
elemStyle = window.getComputedStyle(element);

// style要素に @keyframes ルールの追加
styleSheet.insertRule('@keyframes ' + key + '{' + keyframesRulue + '}', styleSheet.cssRules.length);

// animationend でアニメーションの終わりに実行させる
element.addEventListener('animationend', function animationend() {
element.removeEventListener('animationend', animationend);

// animation-fill-mode: none; 以外の場合の処理
if (elemStyle.animationFillMode !== 'none') {
fillStyle.forEach(function (property) {
element.style.setProperty(property, elemStyle.getPropertyValue(property));
});
}

element.style.animation = '';
resetAnimation(key);

// コールバック関数がある場合は実行
if (typeof callback == 'function') {
callback.call(element);
}
});

// 要素に animation を適用する
element.style.animation = key + ' ' + animationValue;
});
};
})();


使い方としてはこんな感じです。


使用例

/* 要素をいろいろ動かしてみる */

keyframes('#moveBox01', {
// @keyframes に似た構造のオブジェクト
'0%': {
'background-color': '#f00',
'transform': 'translate(0, 0) rotate(0deg)',
},
'50%': {
'background-color': '#0f0',
'transform': 'translate(250px, 0) rotate(0deg)',
},
'100%': {
'background-color': '#00f',
'transform': 'translate(500px, 0) rotate(360deg)',
},
}, {
duration: 2000, // animation-duration と同じ
easing: 'ease', // animation-timing-function と同じ
count: 3, // animation-iteration-count と同じ
direction: 'alternate', // animation-direction と同じ
fill: 'forwards', // animation-fill-mode と同じ
}, function () {
console.log('終わったよ');
});

動的に @keyframes によるアニメーションが実現できました。

(サンプルを置けばわかりやすいのですが)


おわりに

jQueryなしでも結構実現できるものですね。

@keyframes を利用する方法はちょっとめんどくさいですが。