#はじめに
jQueryのアニメーション .animate()
はとても便利ですが、プラグインなしでは色が変わるアニメーションを作れなかったり transform
関係のアニメーションが若干面倒です。
そして、CSS3からはJavaScriptなしでもアニメーションができるようになりましたが、CSSを別に書かなければならないのでJavaScriptと連携させる時に面倒だったりするのも事実。
それならJavaScriptで動的にCSSアニメーションをしてみようという感じです。
※あくまでも参考用のコードなので、何か予期せぬ動作を起こしたりするかもしれません。
#transition
を利用したJavaScriptのアニメーション
単純なものならこんな感じでしょうか。
jQueryの .animate()
に似た表記で書けるように。
// アニメーション用関数
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で動的に指定できたらいいですよね。
(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
を利用する方法はちょっとめんどくさいですが。