Skip to content

Support IE/Edge SVG transforms #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 18, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion src/apply-preserving-inline-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@

(function(scope, testing) {

/**
* 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) {
if (window._webAnimationsUpdateSvgTransformAttr == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use in instead of weak ==.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you not use obfuscation of private vars?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that we don't. We don't obfuscate the tests so this wouldn't work anyway if we did.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

window._webAnimationsUpdateSvgTransformAttr =
/Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent);
}
return window._webAnimationsUpdateSvgTransformAttr;
}

var styleAttributes = {
cssText: 1,
length: 1,
Expand Down Expand Up @@ -44,13 +61,19 @@
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 =
element.namespaceURI &&
element.namespaceURI.indexOf('/svg') != -1 &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make element a parameter to the helper function instead of having the logic split up like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

updateSvgTransformAttr(window);
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 +133,30 @@
_set: function(property, value) {
this._style[property] = value;
this._isAnimatedProperty[property] = true;
if (this._updateSvgTransformAttr &&
scope.canonicalPropertyName(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.canonicalPropertyName(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 +229,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);
5 changes: 5 additions & 0 deletions src/property-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
(function(scope, testing) {

var aliased = {};
var canonical = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the name "canonical" not very obvious, I have to read the logic to understand what it means. Perhaps renaming them as s/aliased/prefixed/ and s/canonical/unprefixed/ would be clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


function alias(name, aliases) {
aliases.concat([name]).forEach(function(candidate) {
if (candidate in document.documentElement.style) {
aliased[name] = candidate;
}
canonical[candidate] = name;
});
}
alias('transform', ['webkitTransform', 'msTransform']);
Expand All @@ -31,5 +33,8 @@
scope.propertyName = function(property) {
return aliased[property] || property;
};
scope.canonicalPropertyName = function(property) {
return canonical[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
116 changes: 116 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);
window._webAnimationsUpdateSvgTransformAttr = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer setup/teardown to delete the member rather than set to null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

});
teardown(function() {
document.documentElement.removeChild(this.element);
document.documentElement.removeChild(this.svgContainer);
window._webAnimationsUpdateSvgTransformAttr = null;
});

test('Style is patched', function() {
Expand Down Expand Up @@ -69,4 +75,114 @@ suite('apply-preserving-inline-style', function() {
this.style.cssText = 'top: 0px';
assert.equal(this.style.length, 1);
});
test('Detect SVG transform compatibility', function() {
var win = {navigator: {userAgent: ''}};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit strange to have this local variable, agent() can just return an object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

function agent(str) {
win._webAnimationsUpdateSvgTransformAttr = null;
win.navigator.userAgent = str;
}
// Unknown data: assume that transforms supported.
assert.equal(updateSvgTransformAttr(win), false);
// Chrome: transforms supported.
agent('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');
assert.equal(updateSvgTransformAttr(win), false);
// Safary: transforms supported.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*Safari

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) ' +
'AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 ' +
'Safari/7046A194A');
assert.equal(updateSvgTransformAttr(win), false);
// Firefox: transforms supported.
agent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) ' +
'Gecko/20100101 Firefox/40.1');
assert.equal(updateSvgTransformAttr(win), false);
// IE: transforms are NOT supported.
agent('Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0;' +
' InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)');
assert.equal(updateSvgTransformAttr(win), true);
// Edge: transforms are NOT supported.
agent('Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36' +
' (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36' +
' Edge/12.10136');
assert.equal(updateSvgTransformAttr(win), true);
// ICS Android: transforms are NOT supported.
agent('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');
assert.equal(updateSvgTransformAttr(win), 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 NOT supported transform on SVG element', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't find the phrase "NOT supported transform on SVG element" very clear. Perhaps "transform CSS property not supported on SVG element".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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 NOT supported prefixed transform 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 NOT supported transform 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)');
});
});