Skip to content

Commit

Permalink
Merge pull request #148 from dvoytenko/ie-svg
Browse files Browse the repository at this point in the history
Support IE/Edge SVG transforms
  • Loading branch information
alancutter authored Jul 18, 2017
2 parents a035866 + eda3251 commit ec226e6
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 4 deletions.
50 changes: 49 additions & 1 deletion src/apply-preserving-inline-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@

(function(scope, testing) {

var SVG_TRANSFORM_PROP = '_webAnimationsUpdateSvgTransformAttr';

/**
* IE/Edge do not support `transform` styles for SVG elements. Instead,
* `transform` attribute can be animated with some restrictions.
* See https://connect.microsoft.com/IE/feedback/details/811744/ie11-bug-with-implementation-of-css-transforms-in-svg,
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/1173754/,
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101242/, etc.
* The same problem is exhibited by pre-Chrome Android browsers (ICS).
* Unfortunately, there's no easy way to feature-detect it.
*/
function updateSvgTransformAttr(window, element) {
if (!element.namespaceURI || element.namespaceURI.indexOf('/svg') == -1) {
return false;
}
if (!(SVG_TRANSFORM_PROP in window)) {
window[SVG_TRANSFORM_PROP] =
/Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent);
}
return window[SVG_TRANSFORM_PROP];
}

var styleAttributes = {
cssText: 1,
length: 1,
Expand Down Expand Up @@ -44,13 +66,16 @@
WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration),
'Element must not already have an animated style attached.');

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

// Copy the inline style contents over to the surrogate.
for (var i = 0; i < this._style.length; i++) {
Expand Down Expand Up @@ -110,9 +135,30 @@
_set: function(property, value) {
this._style[property] = value;
this._isAnimatedProperty[property] = true;
if (this._updateSvgTransformAttr &&
scope.unprefixedPropertyName(property) == 'transform') {
// On IE/Edge, also set SVG element's `transform` attribute to 2d
// matrix of the transform. The `transform` style does not work, but
// `transform` attribute can be used instead.
// Notice, if the platform indeed supports SVG/CSS transforms the CSS
// declaration is supposed to override the attribute.
if (this._savedTransformAttr == null) {
this._savedTransformAttr = this._element.getAttribute('transform');
}
this._element.setAttribute('transform', scope.transformToSvgMatrix(value));
}
},
_clear: function(property) {
this._style[property] = this._surrogateStyle[property];
if (this._updateSvgTransformAttr &&
scope.unprefixedPropertyName(property) == 'transform') {
if (this._savedTransformAttr) {
this._element.setAttribute('transform', this._savedTransformAttr);
} else {
this._element.removeAttribute('transform');
}
this._savedTransformAttr = null;
}
delete this._isAnimatedProperty[property];
},
};
Expand Down Expand Up @@ -185,7 +231,9 @@
}
};

if (WEB_ANIMATIONS_TESTING)
if (WEB_ANIMATIONS_TESTING) {
testing.ensureStyleIsPatched = ensureStyleIsPatched;
testing.updateSvgTransformAttr = updateSvgTransformAttr;
}

})(webAnimations1, webAnimationsTesting);
1 change: 1 addition & 0 deletions src/matrix-decomposition.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,6 @@

scope.dot = dot;
scope.makeMatrixDecomposition = makeMatrixDecomposition;
scope.transformListToMatrix = convertToMatrix;

})(webAnimations1, webAnimationsTesting);
11 changes: 8 additions & 3 deletions src/property-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

(function(scope, testing) {

var aliased = {};
var prefixed = {};
var unprefixed = {};

function alias(name, aliases) {
aliases.concat([name]).forEach(function(candidate) {
if (candidate in document.documentElement.style) {
aliased[name] = candidate;
prefixed[name] = candidate;
}
unprefixed[candidate] = name;
});
}
alias('transform', ['webkitTransform', 'msTransform']);
Expand All @@ -29,7 +31,10 @@
alias('perspectiveOrigin', ['webkitPerspectiveOrigin']);

scope.propertyName = function(property) {
return aliased[property] || property;
return prefixed[property] || property;
};
scope.unprefixedPropertyName = function(property) {
return unprefixed[property] || property;
};

})(webAnimations1, webAnimationsTesting);
13 changes: 13 additions & 0 deletions src/transform-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,19 @@

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

scope.transformToSvgMatrix = function(string) {
// matrix(<a> <b> <c> <d> <e> <f>)
var mat = scope.transformListToMatrix(parseTransform(string));
return 'matrix(' +
numberToLongString(mat[0]) + ' ' + // <a>
numberToLongString(mat[1]) + ' ' + // <b>
numberToLongString(mat[4]) + ' ' + // <c>
numberToLongString(mat[5]) + ' ' + // <d>
numberToLongString(mat[12]) + ' ' + // <e>
numberToLongString(mat[13]) + // <f>
')';
};

if (WEB_ANIMATIONS_TESTING)
testing.parseTransform = parseTransform;

Expand Down
121 changes: 121 additions & 0 deletions test/js/apply-preserving-inline-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ suite('apply-preserving-inline-style', function() {
ensureStyleIsPatched(this.element);
this.style = this.element.style;
document.documentElement.appendChild(this.element);
this.svgContainer = document.createElementNS(
'http://www.w3.org/2000/svg', 'svg');
document.documentElement.appendChild(this.svgContainer);
delete window._webAnimationsUpdateSvgTransformAttr;
});
teardown(function() {
document.documentElement.removeChild(this.element);
document.documentElement.removeChild(this.svgContainer);
delete window._webAnimationsUpdateSvgTransformAttr;
});

test('Style is patched', function() {
Expand Down Expand Up @@ -69,4 +75,119 @@ suite('apply-preserving-inline-style', function() {
this.style.cssText = 'top: 0px';
assert.equal(this.style.length, 1);
});
test('Detect SVG transform compatibility', function() {
var element = document.createElement('div');
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
function check(userAgent, shouldUpdateSvgTransformAttr) {
var win = {navigator: {userAgent: userAgent}};
// Non-SVG element is never updated.
assert.equal(updateSvgTransformAttr(win, element), false);
// SVG element may be updated as tested.
assert.equal(updateSvgTransformAttr(win, svgElement),
shouldUpdateSvgTransformAttr);
}
// Unknown data: assume that transforms supported.
check('', false);
// Chrome: transforms supported.
check('Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E)' +
' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.20' +
' Mobile Safari/537.36',
false);
// Safari: transforms supported.
check('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) ' +
'AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 ' +
'Safari/7046A194A',
false);
// Firefox: transforms supported.
check('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) ' +
'Gecko/20100101 Firefox/40.1',
false);
// IE: transforms are NOT supported.
check('Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0;' +
' InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)',
true);
// Edge: transforms are NOT supported.
check('Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36' +
' (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36' +
' Edge/12.10136',
true);
// ICS Android: transforms are NOT supported.
check('Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; MZ604 Build/I.7.1-45)' +
' AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30',
true);
});
test('Set and clear transform', function() {
// This is not an SVG element, so CSS transform support is not consulted.
window._webAnimationsUpdateSvgTransformAttr = true;
// Set.
this.element.style._set('transform', 'translate(10px, 10px) scale(2)');
assert.equal(getComputedStyle(this.element).transform,
'matrix(2, 0, 0, 2, 10, 10)');
assert.equal(this.element.hasAttribute('transform'), false);
// Clear.
this.element.style._clear('transform');
assert.equal(getComputedStyle(this.element).transform, 'none');
assert.equal(this.element.hasAttribute('transform'), false);
});
test('Set and clear supported transform on SVG element', function() {
window._webAnimationsUpdateSvgTransformAttr = false;
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
ensureStyleIsPatched(svgElement);
this.svgContainer.appendChild(svgElement);
// Set.
svgElement.style._set('transform', 'translate(10px, 10px) scale(2)');
assert.equal(getComputedStyle(svgElement).transform,
'matrix(2, 0, 0, 2, 10, 10)');
assert.equal(svgElement.hasAttribute('transform'), false);
// Clear.
svgElement.style._clear('transform');
assert.equal(getComputedStyle(svgElement).transform, 'none');
assert.equal(svgElement.hasAttribute('transform'), false);
});
test('Set and clear transform CSS property not supported on SVG element', function() {
window._webAnimationsUpdateSvgTransformAttr = true;
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
ensureStyleIsPatched(svgElement);
this.svgContainer.appendChild(svgElement);
// Set.
svgElement.style._set('transform', 'translate(10px, 10px) scale(2)');
assert.equal(getComputedStyle(svgElement).transform,
'matrix(2, 0, 0, 2, 10, 10)');
assert.equal(svgElement.getAttribute('transform'),
'matrix(2 0 0 2 10 10)');
// Clear.
svgElement.style._clear('transform');
assert.equal(getComputedStyle(svgElement).transform, 'none');
assert.equal(svgElement.getAttribute('transform'), null);
});
test('Set and clear prefixed transform CSS property not supported on SVG element', function() {
window._webAnimationsUpdateSvgTransformAttr = true;
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
ensureStyleIsPatched(svgElement);
this.svgContainer.appendChild(svgElement);
// Set.
svgElement.style._set('msTransform', 'translate(10px, 10px) scale(2)');
assert.equal(svgElement.getAttribute('transform'),
'matrix(2 0 0 2 10 10)');
// Clear.
svgElement.style._clear('msTransform');
assert.equal(svgElement.getAttribute('transform'), null);
});
test('Restore transform CSS property not supported on SVG element', function() {
window._webAnimationsUpdateSvgTransformAttr = true;
var svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
svgElement.setAttribute('transform', 'matrix(2 0 0 2 0 0)');
ensureStyleIsPatched(svgElement);
this.svgContainer.appendChild(svgElement);
// Set.
svgElement.style._set('transform', 'translate(10px, 10px) scale(2)');
assert.equal(getComputedStyle(svgElement).transform,
'matrix(2, 0, 0, 2, 10, 10)');
assert.equal(svgElement.getAttribute('transform'),
'matrix(2 0 0 2 10 10)');
// Clear.
svgElement.style._clear('transform');
assert.equal(getComputedStyle(svgElement).transform, 'none');
assert.equal(svgElement.getAttribute('transform'), 'matrix(2 0 0 2 0 0)');
});
});

0 comments on commit ec226e6

Please sign in to comment.