Skip to content

Commit ec226e6

Browse files
authored
Merge pull request #148 from dvoytenko/ie-svg
Support IE/Edge SVG transforms
2 parents a035866 + eda3251 commit ec226e6

5 files changed

+192
-4
lines changed

src/apply-preserving-inline-style.js

+49-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@
1414

1515
(function(scope, testing) {
1616

17+
var SVG_TRANSFORM_PROP = '_webAnimationsUpdateSvgTransformAttr';
18+
19+
/**
20+
* IE/Edge do not support `transform` styles for SVG elements. Instead,
21+
* `transform` attribute can be animated with some restrictions.
22+
* See https://connect.microsoft.com/IE/feedback/details/811744/ie11-bug-with-implementation-of-css-transforms-in-svg,
23+
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/1173754/,
24+
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101242/, etc.
25+
* The same problem is exhibited by pre-Chrome Android browsers (ICS).
26+
* Unfortunately, there's no easy way to feature-detect it.
27+
*/
28+
function updateSvgTransformAttr(window, element) {
29+
if (!element.namespaceURI || element.namespaceURI.indexOf('/svg') == -1) {
30+
return false;
31+
}
32+
if (!(SVG_TRANSFORM_PROP in window)) {
33+
window[SVG_TRANSFORM_PROP] =
34+
/Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent);
35+
}
36+
return window[SVG_TRANSFORM_PROP];
37+
}
38+
1739
var styleAttributes = {
1840
cssText: 1,
1941
length: 1,
@@ -44,13 +66,16 @@
4466
WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration),
4567
'Element must not already have an animated style attached.');
4668

69+
this._element = element;
4770
// Stores the inline style of the element on its behalf while the
4871
// polyfill uses the element's inline style to simulate web animations.
4972
// This is needed to fake regular inline style CSSOM access on the element.
5073
this._surrogateStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style;
5174
this._style = element.style;
5275
this._length = 0;
5376
this._isAnimatedProperty = {};
77+
this._updateSvgTransformAttr = updateSvgTransformAttr(window, element);
78+
this._savedTransformAttr = null;
5479

5580
// Copy the inline style contents over to the surrogate.
5681
for (var i = 0; i < this._style.length; i++) {
@@ -110,9 +135,30 @@
110135
_set: function(property, value) {
111136
this._style[property] = value;
112137
this._isAnimatedProperty[property] = true;
138+
if (this._updateSvgTransformAttr &&
139+
scope.unprefixedPropertyName(property) == 'transform') {
140+
// On IE/Edge, also set SVG element's `transform` attribute to 2d
141+
// matrix of the transform. The `transform` style does not work, but
142+
// `transform` attribute can be used instead.
143+
// Notice, if the platform indeed supports SVG/CSS transforms the CSS
144+
// declaration is supposed to override the attribute.
145+
if (this._savedTransformAttr == null) {
146+
this._savedTransformAttr = this._element.getAttribute('transform');
147+
}
148+
this._element.setAttribute('transform', scope.transformToSvgMatrix(value));
149+
}
113150
},
114151
_clear: function(property) {
115152
this._style[property] = this._surrogateStyle[property];
153+
if (this._updateSvgTransformAttr &&
154+
scope.unprefixedPropertyName(property) == 'transform') {
155+
if (this._savedTransformAttr) {
156+
this._element.setAttribute('transform', this._savedTransformAttr);
157+
} else {
158+
this._element.removeAttribute('transform');
159+
}
160+
this._savedTransformAttr = null;
161+
}
116162
delete this._isAnimatedProperty[property];
117163
},
118164
};
@@ -185,7 +231,9 @@
185231
}
186232
};
187233

188-
if (WEB_ANIMATIONS_TESTING)
234+
if (WEB_ANIMATIONS_TESTING) {
189235
testing.ensureStyleIsPatched = ensureStyleIsPatched;
236+
testing.updateSvgTransformAttr = updateSvgTransformAttr;
237+
}
190238

191239
})(webAnimations1, webAnimationsTesting);

src/matrix-decomposition.js

+1
Original file line numberDiff line numberDiff line change
@@ -435,5 +435,6 @@
435435

436436
scope.dot = dot;
437437
scope.makeMatrixDecomposition = makeMatrixDecomposition;
438+
scope.transformListToMatrix = convertToMatrix;
438439

439440
})(webAnimations1, webAnimationsTesting);

src/property-names.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414

1515
(function(scope, testing) {
1616

17-
var aliased = {};
17+
var prefixed = {};
18+
var unprefixed = {};
1819

1920
function alias(name, aliases) {
2021
aliases.concat([name]).forEach(function(candidate) {
2122
if (candidate in document.documentElement.style) {
22-
aliased[name] = candidate;
23+
prefixed[name] = candidate;
2324
}
25+
unprefixed[candidate] = name;
2426
});
2527
}
2628
alias('transform', ['webkitTransform', 'msTransform']);
@@ -29,7 +31,10 @@
2931
alias('perspectiveOrigin', ['webkitPerspectiveOrigin']);
3032

3133
scope.propertyName = function(property) {
32-
return aliased[property] || property;
34+
return prefixed[property] || property;
35+
};
36+
scope.unprefixedPropertyName = function(property) {
37+
return unprefixed[property] || property;
3338
};
3439

3540
})(webAnimations1, webAnimationsTesting);

src/transform-handler.js

+13
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,19 @@
256256

257257
scope.addPropertiesHandler(parseTransform, mergeTransforms, ['transform']);
258258

259+
scope.transformToSvgMatrix = function(string) {
260+
// matrix(<a> <b> <c> <d> <e> <f>)
261+
var mat = scope.transformListToMatrix(parseTransform(string));
262+
return 'matrix(' +
263+
numberToLongString(mat[0]) + ' ' + // <a>
264+
numberToLongString(mat[1]) + ' ' + // <b>
265+
numberToLongString(mat[4]) + ' ' + // <c>
266+
numberToLongString(mat[5]) + ' ' + // <d>
267+
numberToLongString(mat[12]) + ' ' + // <e>
268+
numberToLongString(mat[13]) + // <f>
269+
')';
270+
};
271+
259272
if (WEB_ANIMATIONS_TESTING)
260273
testing.parseTransform = parseTransform;
261274

test/js/apply-preserving-inline-style.js

+121
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ suite('apply-preserving-inline-style', function() {
44
ensureStyleIsPatched(this.element);
55
this.style = this.element.style;
66
document.documentElement.appendChild(this.element);
7+
this.svgContainer = document.createElementNS(
8+
'http://www.w3.org/2000/svg', 'svg');
9+
document.documentElement.appendChild(this.svgContainer);
10+
delete window._webAnimationsUpdateSvgTransformAttr;
711
});
812
teardown(function() {
913
document.documentElement.removeChild(this.element);
14+
document.documentElement.removeChild(this.svgContainer);
15+
delete window._webAnimationsUpdateSvgTransformAttr;
1016
});
1117

1218
test('Style is patched', function() {
@@ -69,4 +75,119 @@ suite('apply-preserving-inline-style', function() {
6975
this.style.cssText = 'top: 0px';
7076
assert.equal(this.style.length, 1);
7177
});
78+
test('Detect SVG transform compatibility', function() {
79+
var element = document.createElement('div');
80+
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
81+
function check(userAgent, shouldUpdateSvgTransformAttr) {
82+
var win = {navigator: {userAgent: userAgent}};
83+
// Non-SVG element is never updated.
84+
assert.equal(updateSvgTransformAttr(win, element), false);
85+
// SVG element may be updated as tested.
86+
assert.equal(updateSvgTransformAttr(win, svgElement),
87+
shouldUpdateSvgTransformAttr);
88+
}
89+
// Unknown data: assume that transforms supported.
90+
check('', false);
91+
// Chrome: transforms supported.
92+
check('Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E)' +
93+
' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.20' +
94+
' Mobile Safari/537.36',
95+
false);
96+
// Safari: transforms supported.
97+
check('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) ' +
98+
'AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 ' +
99+
'Safari/7046A194A',
100+
false);
101+
// Firefox: transforms supported.
102+
check('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) ' +
103+
'Gecko/20100101 Firefox/40.1',
104+
false);
105+
// IE: transforms are NOT supported.
106+
check('Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0;' +
107+
' InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)',
108+
true);
109+
// Edge: transforms are NOT supported.
110+
check('Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36' +
111+
' (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36' +
112+
' Edge/12.10136',
113+
true);
114+
// ICS Android: transforms are NOT supported.
115+
check('Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; MZ604 Build/I.7.1-45)' +
116+
' AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30',
117+
true);
118+
});
119+
test('Set and clear transform', function() {
120+
// This is not an SVG element, so CSS transform support is not consulted.
121+
window._webAnimationsUpdateSvgTransformAttr = true;
122+
// Set.
123+
this.element.style._set('transform', 'translate(10px, 10px) scale(2)');
124+
assert.equal(getComputedStyle(this.element).transform,
125+
'matrix(2, 0, 0, 2, 10, 10)');
126+
assert.equal(this.element.hasAttribute('transform'), false);
127+
// Clear.
128+
this.element.style._clear('transform');
129+
assert.equal(getComputedStyle(this.element).transform, 'none');
130+
assert.equal(this.element.hasAttribute('transform'), false);
131+
});
132+
test('Set and clear supported transform on SVG element', function() {
133+
window._webAnimationsUpdateSvgTransformAttr = false;
134+
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
135+
ensureStyleIsPatched(svgElement);
136+
this.svgContainer.appendChild(svgElement);
137+
// Set.
138+
svgElement.style._set('transform', 'translate(10px, 10px) scale(2)');
139+
assert.equal(getComputedStyle(svgElement).transform,
140+
'matrix(2, 0, 0, 2, 10, 10)');
141+
assert.equal(svgElement.hasAttribute('transform'), false);
142+
// Clear.
143+
svgElement.style._clear('transform');
144+
assert.equal(getComputedStyle(svgElement).transform, 'none');
145+
assert.equal(svgElement.hasAttribute('transform'), false);
146+
});
147+
test('Set and clear transform CSS property not supported on SVG element', function() {
148+
window._webAnimationsUpdateSvgTransformAttr = true;
149+
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
150+
ensureStyleIsPatched(svgElement);
151+
this.svgContainer.appendChild(svgElement);
152+
// Set.
153+
svgElement.style._set('transform', 'translate(10px, 10px) scale(2)');
154+
assert.equal(getComputedStyle(svgElement).transform,
155+
'matrix(2, 0, 0, 2, 10, 10)');
156+
assert.equal(svgElement.getAttribute('transform'),
157+
'matrix(2 0 0 2 10 10)');
158+
// Clear.
159+
svgElement.style._clear('transform');
160+
assert.equal(getComputedStyle(svgElement).transform, 'none');
161+
assert.equal(svgElement.getAttribute('transform'), null);
162+
});
163+
test('Set and clear prefixed transform CSS property not supported on SVG element', function() {
164+
window._webAnimationsUpdateSvgTransformAttr = true;
165+
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
166+
ensureStyleIsPatched(svgElement);
167+
this.svgContainer.appendChild(svgElement);
168+
// Set.
169+
svgElement.style._set('msTransform', 'translate(10px, 10px) scale(2)');
170+
assert.equal(svgElement.getAttribute('transform'),
171+
'matrix(2 0 0 2 10 10)');
172+
// Clear.
173+
svgElement.style._clear('msTransform');
174+
assert.equal(svgElement.getAttribute('transform'), null);
175+
});
176+
test('Restore transform CSS property not supported on SVG element', function() {
177+
window._webAnimationsUpdateSvgTransformAttr = true;
178+
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
179+
svgElement.setAttribute('transform', 'matrix(2 0 0 2 0 0)');
180+
ensureStyleIsPatched(svgElement);
181+
this.svgContainer.appendChild(svgElement);
182+
// Set.
183+
svgElement.style._set('transform', 'translate(10px, 10px) scale(2)');
184+
assert.equal(getComputedStyle(svgElement).transform,
185+
'matrix(2, 0, 0, 2, 10, 10)');
186+
assert.equal(svgElement.getAttribute('transform'),
187+
'matrix(2 0 0 2 10 10)');
188+
// Clear.
189+
svgElement.style._clear('transform');
190+
assert.equal(getComputedStyle(svgElement).transform, 'none');
191+
assert.equal(svgElement.getAttribute('transform'), 'matrix(2 0 0 2 0 0)');
192+
});
72193
});

0 commit comments

Comments
 (0)