IE11でもbackground-sizeをアニメーション
CSS Animationのbackground-sizeのアニメ化をIE11に適用させるJavaScript
IE11にはいろいろと毀誉褒貶があるようだが、Web標準の実装では今や他のブラウザとほとんど遜色ないし、個人的には良くできたブラウザだと思う。 そんなIE11だが、CSSのAnimation関連で、いくつかアニメ化が実装されなかったプロパティがある。
- IE11に欠けているアニメーション可能な主なプロパティ
-
- background-size
- flexbox関連のプロパティのいくつか
- multi-column関連のプロパティのいくつか
- font-size-adjust / font-stretch / outline-offset / text-decoration-colorなど
flexboxとかmulti-columnは最近になって勧告候補(Candidate Recommendation)になったばかりだし、ほかのプロパティもまだ草案(Working Draft)段階だったりするものもあるので、それらが実装されなかったのはまぁしょうがない、という気がする。 でも、残念なのが、background-sizeまでもが動かせないこと。
そこで、例によって、JavaScriptで代替実装させてみた。
サンプルはこちら。
使用前、
使用後
Win7のIE11で動作確認済み。 IE10も、たぶん大丈夫でしょう、…おそらく、…きっと、…うまくすればw。 IE10でもとりあえず動作確認できました
使用方法と制限事項
- ソースのanimateBgSizeSettings内に、アニメーションさせる要素(ターゲット)のCSSセレクタとanimation-name(@keyframesの名前)を記述。
-
tがターゲット要素、nがアニメーション名。
pはオプション(省略可)で、一時停止・再生させたい場合にトリガーとなるイベント名を記述。 -
複数の設定をまとめて記述する場合には、{ }, { }とオブジェクトをカンマで繋げればOK。
例:{ t: '#animateMe', n: 'aniMania', p : 'click' }, { t: '.animateUs', n: 'aniMate' }
-
tのターゲット要素に、/^#[\-]?[a-z_][a-z0-9_\-]+$/i(「#」で始まり、以降はアルファベット(大文字小文字問わず)と数字、アンダースコア、ハイフンのみの組み合わせ、…手抜き実装だし、説明も手抜きですがw)にマッチする形でid値を指定した場合、多少早くなります(たぶん)。
一方、複数の要素に対して同じアニメーションを適用させたい場合は、tにはカンマ区切りで複数の要素をまとめて指定できます。
例:{ t: '#animateMe, #animateYou', n: 'aniMania' }
- 同様にpのイベントもカンマ区切りでまとめて指定できます。
-
一方、1つのオブジェクトにつき、nに入れられる値(animation-name)は1つだけとしています。
CSS上で1つの要素に対して複数のアニメーションを適用させている場合などには、CSSのようにanimation-nameのリスト全てをnに列記するのではなく、background-sizeを動かしているanimation-nameのうちでリストの最後に最も近いものを記述してください。
例えば、{ t: '.animateUs', n: 'aniMate, aniMania' }
このような指定は無効となります。 -
@keyframesの中でbackground-sizeしか記述されていない場合、IEはアニメーションさせるプロパティがないと判断してその@keyframesを無視してしまうため、動きません(animationstartが発火しない)。
(無駄ですが)IEでアニメーション可能なプロパティを併記してください(fromとtoの値を一致させておくなどしておけばOK)。
@keyframes aniMate { from { width: 100%; background-size: 0 auto; } to { width: 100%; background-size: 100% auto; } }
- ::before / ::after擬似要素には対応していません(インラインのstyle属性では擬似要素のスタイルの設定ができないため)。
- アニメーションの使用方法やユーザーの閲覧環境によっては、サイトをモッサリさせてしまうかもしれません。 アニメーションはそうでなくても負荷が高めで、それに加えてJavaScriptも動かしているわけなので、使い過ぎにはご注意を(※個人の体験に基づく感想ですw)。
スクリプトソース (最終更新:2016/2/5)
/**
* animateBackgroundSize()
* IE(10~11)にCSS Animationでのbackground-sizeのアニメ化を適用
* Copyright (c) 2013-2016 Kazz
* http://asamuzak.jp
* Dual licensed under MIT or GPL
* http://asamuzak.jp/license
*/
(function(_win, _doc) {
"use strict";
function animateBackgroundSize() {
// User Settings
// t - querySelector of the target element.
// ターゲットのセレクタ(querySelector) (例) "#animateMe"
// n - a name of @keyframes (animation-name).
// keyframesの名前(animation-nameの値) (例) "aniMania"
// p - event trigger to pause / restart animation.
// 一時停止のトリガーevent(オプション) (例) "click"
// 複数指定する場合は { }, { } のようにカンマで繋ぐ
var animateBgSizeSettings = [
// { t: "#animateMe", n: "zoomInBg", p: "click, dblclick" },
// { t: ".mirror_initial", n: "transformInitial" }
];
var aBg = {},
_cNum = (function(n) { return function() { return n++; };})(0);
function getBezierSplineAtP(x1, y1, x2, y2, p) {
var x;
if(x1 === y1 && x2 === y2) {
x = p;
}
else {
for(var y = p, a = (1 - 3 * (x2 - x1)), b = (3 * (x2 - 2 * x1)), c = (3 * x1),
i = 0; i < 4; i++) {
x = 3 * a * y * y + 2 * b * y + c;
if(x === 0) {
break;
}
y -= (((a * y + b) * y + c) * y - p) / x;
}
x = (((1 - 3 * (y2 - y1)) * y + 3 * (y2 - 2 * y1)) * y + 3 * y1) * y;
}
return x.toFixed(4);
}
function getSteps(p, s) {
for(var x = s[1] * 1, y = s[2], z = 1 / x, i = 0, l = x; i <= l; i++) {
switch(y) {
case "start":
switch(true) {
case p === 0:
p = z;
break;
case (p === 1 || i === l):
p = 1;
break;
case (i > 0 && i < l && p === z * i):
p = z * (i + 1);
break;
case (i > 0 && p > z * (i - 1) && p < z * i):
p = z * i;
break;
}
break;
case "end":
switch(true) {
case (p === 0 || p === 1 || p === z * i):
break;
case (i === l):
p = 1;
break;
case (i > 0 && p > z * (i - 1) && p < z * i):
p = z * (i - 1);
break;
}
break;
}
}
return p;
}
function setCalcVal(t, f, c) {
var x,
y = /^([0-9]+(?:\.[0-9]+)?)([a-z%]+)?$/,
tv = (x = y.exec(t)) && x[1] * 1,
tu = x[2] ? x[2] : "",
fv = (x = y.exec(f)) && x[1] * 1,
fu = x[2] ? x[2] : "";
return c === 0 ? f :
c === 1 ? t :
tv === 0 ? fv * (1 - c) + fu :
fv === 0 ? tv * c + tu :
tu === fu ? ((tv - fv) * c + fv) + tu :
"calc((" + t + "-" + f + ")*" + c + "+" + f + ")";
}
function convKeywordToBezier(k) {
switch(k) {
case "ease":
k = "cubic-bezier(0.25,0.1,0.25,1)";
break;
case "linear":
k = "cubic-bezier(0,0,1,1)";
break;
case "ease-in":
k = "cubic-bezier(0.42,0,1,1)";
break;
case "ease-out":
k = "cubic-bezier(0,0,0.58,1)";
break;
case "ease-in-out":
k = "cubic-bezier(0.42,0,0.58,1)";
break;
default:
k = /cubic\-bezier/.test(k) ? k : false;
}
return k;
}
function convSecToMiliSec(s) {
return s && (s = /^(\-?[0-9]*(?:\.[0-9]+)?)(m?s)$/.exec(s)) ?
/ms/.test(s[2]) ? s[1] * 1 :
s[1] * 1000 : 0;
}
function convKeyToCoef(k) {
return (k = /^([0-9]+(?:\.[0-9]+)?)%?$/.exec(k)) ? k[1] / 100 : 1;
}
function getKeyframes(s) {
for(var y, z = _doc.styleSheets, i = 0, l = z.length; i < l; i++) {
for(var j = 0, k = z[i].cssRules.length; j < k; j++) {
var a = z[i].cssRules[j];
if(a.type === 7 && a.name === s) {
y = a;
break;
}
}
}
return y;
}
function getBgKeyframesValue(s) {
function getBgKeyframeRule(k) {
for(var y = [], z, i = 0, l = k.cssRules.length; i < l; i++) {
k.cssRules[i].type === 8 && (
z = /background\-size\s*:\s*(.+);/.exec(k.cssRules[i].cssText)
) &&
y.push({ key: k.cssRules[i].keyText, rule: z[1] });
}
return y;
}
function convRuleToArr(r) {
var a = [];
r = r.split(",");
for(var i = 0, l = r.length; i < l; i++) {
var b = r[i].replace(/^\s*/, "").replace(/\s*$/, "").split(/\s+/);
a[i] = {
w: b[0],
h: b.length === 2 ? b[1] : /co(?:ntain|ver)/.test(b[0]) ? "" : "auto"
};
}
return a;
}
function setKeyframeBgSize(a) {
var y = [], z = 0, i, l, j, k, w, h;
for(i = 0, l = a.length; i < l; i++) {
y[i] = {
coef: convKeyToCoef(a[i].key),
val: convRuleToArr(a[i].rule)
};
}
for(i = 0, l = y.length; i < l; i++) {
z = Math.max(z, y[i].val.length);
}
for(i = 0, l = y.length; i < l; i++) {
for(j = 0, k = z; j < k; j++) {
y[i].val.length < z && (y[i].val[y[i].val.length] = y[i].val[j]);
}
}
for(i = 0, l = z; i < l; i++) {
for(w = "", h = "", j = 0, k = y.length; j < k; j++) {
w += y[j].val[i].w;
h += y[j].val[i].h;
}
if(/auto|co(?:ntain|ver)/.test(w)) {
for(w = y[y.length - 1].val[i].w, j = 0, k = y.length; j < k; j++) {
y[j].val[i].w = w;
}
}
if(/auto|co(?:ntain|ver)/.test(h)) {
for(h = y[y.length - 1].val[i].h, j = 0, k = y.length; j < k; j++) {
y[j].val[i].h = h;
}
}
}
return z > 0 ? y : [];
}
var x = getKeyframes(s);
x && (
x = getBgKeyframeRule(x), x = x.length > 1 ?
setKeyframeBgSize(x) : []
);
return x ? x : [];
}
function getAnimationOrdinal(v, s) {
var x = null;
if(/,/.test(v) && s) {
v = v.replace(/\s+/g, "").split(",");
for(var i = 0, l = v.length; i < l; i++) {
if(v[i] === s) {
x = i;
break;
}
}
}
return x;
}
function getPropValFromComputedStyle(v, n) {
v = v.replace(/\s+/g, "");
return /,/.test(v) && !/^(?:cubic\-bezier\([\-\.0-9,]+\)|steps\([0-9]+,(?:start|end)\))$/.test(v) && n !== null ?
(v = /(?:cubic\-bezier\([\-\.0-9,]+\)|steps\([0-9]+,(?:start|end)\)),/.test(v) ?
v.split(/,(?!(?:[\-\.0-9]|(?:start|end)\)))/) :
v.split(","), v[n * 1]) :
v;
}
function runBgSizeAnimation(tObj, aObj) {
function setBgSizeValue(c) {
for(var x = aObj.aVal, y = "", z = /auto|co(?:ntain|ver)/,
f, t, i = 1, l = x.length; i < l; i++) {
if((t = x[i], f = x[i - 1]) && t.coef >= c) {
for(var j = 0, k = t.val.length; j < k; j++) {
y = z.test(t.val[j].w) || t.val[j].w === f.val[j].w ?
y + t.val[j].w + " ":
y + setCalcVal(t.val[j].w, f.val[j].w, c) + " ";
y = z.test(t.val[j].h) || t.val[j].h === f.val[j].h ?
y + t.val[j].h + "," :
y + setCalcVal(t.val[j].h, f.val[j].h, c) + ",";
}
break;
}
}
return y.replace(/,$/, "");
}
function animateBgSize(t) {
var a,
p = (a = ((t - aObj.aStart - aObj.aDly) / aObj.aDur).toFixed(4) * 1) &&
aObj.aCount % 2 === 1 ?
a : 1 - a,
c = (a = /cubic\-bezier\(\s*([01]?(?:\.[0-9]+)?)\s*,\s*([01]?(?:\.[0-9]+)?)\s*,\s*([01]?(?:\.[0-9]+)?)\s*,\s*([01]?(?:\.[0-9]+)?)\s*\)/.exec(aObj.aFunc)) ?
getBezierSplineAtP(a[1] * 1, a[2] * 1, a[3] * 1, a[4] * 1, p) * 1 :
(a = /steps\(\s*([0-9]+)\s*,\s*(start|end)\s*\)/.exec(aObj.aFunc)) ?
getSteps(p, a) : aObj.aCount % 2 === 1 ?
1 : 0;
tObj.style.setProperty("background-size", setBgSizeValue(c));
(c > 0 && aObj.aCount % 2 === 0 || c < 1 && aObj.aCount % 2 === 1) && (
aObj.doAnimate = _win.requestAnimationFrame(animateBgSize)
);
}
function pauseAnimate(t) {
var x = getPropValFromComputedStyle(
_win.getComputedStyle(tObj).animationPlayState,
aObj.aOrder
);
aObj.aStop !== null && (
(x === "paused" && aObj.aStop === 0) ||
(x === "running" && aObj.aStop > 0)
) && (
x === "paused" ? (
aObj.aStop = t, aObj.aDly -= (aObj.aStop - aObj.aStart),
_win.cancelAnimationFrame(aObj.doAnimate)
) : x === "running" && (
aObj.aStart = t,
aObj.aStop = 0,
aObj.doAnimate = _win.requestAnimationFrame(animateBgSize)
)
);
}
function cuePauseAnimate() {
aObj.aStop !== null && _win.requestAnimationFrame(pauseAnimate);
}
function toggleAnimationEvent(evt) {
var x;
if(evt.currentTarget === tObj && evt.animationName === aObj.aName) {
switch(evt.type) {
case "animationstart":
aObj.aStart = _win.performance.now();
aObj.aStop = 0;
aObj.doAnimate = _win.requestAnimationFrame(animateBgSize);
break;
case "animationiteration":
aObj.aStart = _win.performance.now();
aObj.aDly = 0;
/alternate/.test(getPropValFromComputedStyle(
_win.getComputedStyle(tObj).animationDirection, aObj.aOrder
)) &&
aObj.aCount++;
aObj.doAnimate = _win.requestAnimationFrame(animateBgSize);
break;
case "animationend":
_win.cancelAnimationFrame(aObj.doAnimate);
(x = getPropValFromComputedStyle(
_win.getComputedStyle(tObj).animationFillMode, aObj.aOrder)
) && /both|forwards/.test(x) ?
tObj.style.setProperty("background-size", setBgSizeValue(1)) :
/none|backwards/.test(x) && tObj.style.removeProperty("background-size");
break;
case "error":
_win.cancelAnimationFrame(aObj.doAnimate);
tObj.style.removeProperty("background-size");
break;
}
}
}
function setAnimationProp() {
var a = _win.getComputedStyle(tObj),
x = getAnimationOrdinal(a.animationName, aObj.aName),
y;
aObj.aOrder = x;
aObj.aVal = getBgKeyframesValue(aObj.aName);
aObj.aDur = convSecToMiliSec(getPropValFromComputedStyle(a.animationDuration, x));
aObj.aDly = (y = convSecToMiliSec(
getPropValFromComputedStyle(a.animationDelay, x)
)) < 0 ? y : 0;
aObj.aFunc = convKeywordToBezier(
getPropValFromComputedStyle(a.animationTimingFunction, x)
);
aObj.aCount = /reverse/.test(getPropValFromComputedStyle(a.animationDirection, x)) ?
0 : 1;
if(aObj.aDur > 0 && aObj.aFunc && aObj.aVal.length > 1) {
/b(?:ackwards|oth)/.test(getPropValFromComputedStyle(a.animationFillMode, x)) &&
tObj.style.setProperty("background-size", setBgSizeValue(0));
tObj.addEventListener("animationstart", toggleAnimationEvent, false);
tObj.addEventListener("animationiteration", toggleAnimationEvent, false);
tObj.addEventListener("animationend", toggleAnimationEvent, false);
tObj.addEventListener("error", toggleAnimationEvent, false);
if(aObj.aPause) {
y = aObj.aPause.split(",");
for(var i = 0, l = y.length; i < l; i++) {
_win.addEventListener(y[i].replace(/\s+/g, ""), cuePauseAnimate, true);
}
}
aObj.aTime > 0 && (aObj.doAnimate = _win.requestAnimationFrame(animateBgSize));
}
}
setAnimationProp();
}
(function(a) {
function setABgObj(o, p) {
var q = "aBgId" + _cNum();
aBg[q] = {
aName: p.n,
aPause: p.p ? p.p : null,
aStart: null,
aStop: null,
aOrder: null,
aVal: null,
aDur: 0,
aDly: 0,
aFunc: null,
aCount: null,
doAnimate: null
};
runBgSizeAnimation(o, aBg[q]);
}
for(var x, z, i = 0, l = a.length; i < l; i++) {
var y = a[i];
if((x = /^#([\-]?[a-z_][a-z0-9_\-]+)$/i.exec(y.t)) &&
(z = _doc.getElementById(x[1]))) {
setABgObj(z, y);
}
else {
z = _doc.querySelectorAll(y.t);
for(var j = 0, k = z.length; j < k; j++) {
setABgObj(z[j], y);
}
}
}
})(animateBgSizeSettings);
}
_doc.documentMode && _win.requestAnimationFrame &&
_doc.addEventListener("DOMContentLoaded", animateBackgroundSize, false);
})(window, document);
"IE11でもbackground-sizeをアニメーション"へのTwitter上でのコメントやRT
23件のツイートがあります。
ツイート 1
asamuzaK.jp : IE11でもbackground-sizeをアニメーション / MSIE11(たぶんMSIE10も)にCSS Animationでのbackground-sizeのアニメ化を適用させるJavaScript http://t.co/AeeBjX7g4q
ツイート 2
あ、animation-fill-modeへの対応が抜けていることに今気付いた…orz 時間ができたら直さなきゃ / asamuzaK.jp : IE11でもbackground-sizeをアニメーション / http://t.co/AeeBjX7g4q
ツイート 3
つーても、animationendでスクリプトで適用したスタイルは剥がしているので実質的にはアニメーション実行前への対応が必要なだけかな? / asamuzaK.jp : IE11でもbackground-sizeをアニメーション / http://t.co/AeeBjX7g4q
ツイート 4
そうでもないや、toの値がCSSでのデフォルトの値と一致しているとは限らないから、やっぱanimationendの処理も必要だわ… / http://t.co/AeeBjX7g4q
ツイート 5
スクリプトをあれやこれやと微調整 停止・再生を繰り返しても一応バグらなくなった 完全にバグ取りできたかはわからないけど…w あと、IE10でも動作することを確認できた / http://t.co/AeeBjX7g4q
ツイート 6
あ、そうそう IE11の肩を持つことを言えば、今回のスクリプトではIEの独自実装はIE11(と10)の判別にしか使ってない 後は全部Web標準で機能を実装できた つまり、かつてのIEを意識する必要がない程IE11はかなり優秀だよ / http://t.co/AeeBjX7g4q
ツイート 7
前にMozillaのなかのさんが言ってたけど、日本人はバグがあってもその場しのぎで何とかしようとして(って自分にも当てはまるがw)、バグを発見してもシェアしないことが多い。とのことで、それじゃあいつまで経ってもそのバグは残り続けるよね http://t.co/AeeBjX7g4q
ツイート 8
バグは開発した側が気づいてないからバグなのであって、気づいていたら「それは仕様」とか言い張るはずw つーことで、このスクリプトに関しても、もうバグはないと個人的には思ってるけどきっと残っているはずなので、気づいた方はぜひご一報をw http://t.co/AeeBjX7g4q
ツイート 9
…と大見得切って言い放った矢先にバグの芽を発見orz 修正した http://t.co/AeeBjX7g4q
ツイート 10
スクリプトを整理 / asamuzaK.jp : IE11でもbackground-sizeをアニメーション / CSS Animationのbackground-sizeのアニメ化をIE11に適用させるJavaScript http://t.co/AeeBjX7g4q
ツイート 11
スクリプト更新、記事も整理 / asamuzaK.jp : IE11でもbackground-sizeをアニメーション / CSS Animationのbackground-sizeのアニメ化をIE11に適用させるJavaScript http://t.co/AeeBjX7g4q
ツイート 12
スクリプト更新。JSHintにfor文の中で関数定義なんかするなと怒られたので修正した / asamuzaK.jp : IE11でもbackground-sizeをアニメーション http://t.co/AeeBjX7g4q
ツイート 13
.@moro84 具体例としては http://t.co/3G7KFMmC4u の中のテストサンプル http://t.co/E9jq6McyzQ を参照ください body要素に、背景色のグラデーションを変えたらクルマが登場するという連続的なアニメーションを適用しています
ツイート 14
ちなみにこれね asamuzaK.jp : IE11でもbackground-sizeをアニメーション http://t.co/3G7KFMmC4u
ツイート 15
でもたぶんこういうことがやりたいんだろうな 今度は、background-sizeをアニメーションさせて http://t.co/AeeBjX7g4q のスクリプトを適用させてみたもの http://t.co/bJwMTdL0sf http://t.co/ui2FkYUpli
ツイート 16
久しぶりにスクリプトを更新 asamuzaK.jp : IE11でもbackground-sizeをアニメーション http://t.co/uktZJcKTC9
ツイート 17
つーことは、connectに投げてまだ解決してないissueは、Spartan(Edge)でもそのままになっちゃうのだろうか…?orz http://t.co/6JNbw8f6Wq https://t.co/q7F4I82TJS
ツイート 18
スクリプト更新。関数を整理 asamuzaK.jp : IE11でもbackground-sizeをアニメーション ~ CSS Animationのbackground-sizeのアニメ化をIE11に適用させるJavaScript http://t.co/6JNbw7XvxQ
ツイート 19
うちで公開してるIE向けスクリプトは IE9用の http://t.co/CBoNSpEROd とIE10+用の http://t.co/6JNbw7XvxQ 位だけど、後者はconnectに投げたissueに進展がなくSpartanでも使わなくてはいけないのかと、ちと不安orz
ツイート 20
ベジェ曲線から値を取得する関数を整理してみた http://t.co/6JNbw7XvxQ http://t.co/1jHxp0yV7V
ツイート 21
MS edgeのテスト(続き) background-sizeのCSSアニメーションは実装したようだけどかなりバギーorz http://t.co/6JNbw7XvxQ matrix()を使ったアニメーションもOK♪ http://t.co/Q87LJOf1KN
ツイート 22
Edgeで追試 http://t.co/6JNbw7XvxQ にある http://t.co/E9jq6MtBBQ は、Gradientsもアニメーションさせて高負荷のせいかコマ落ち http://t.co/LRTvjyD1XY こちらでテストしたらきれいにアニメーションした
ツイート 23
おそらく(ほぼ間違いなく)IEはfillをanimatableなプロパティとして認識していないためで、もしIEでもfillをアニメーションさせたかったら https://t.co/6JNbw7XvxQ こんな感じで力技で独自実装するしかない鴨