-
Notifications
You must be signed in to change notification settings - Fork 409
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
window._webAnimationsUpdateSvgTransformAttr = | ||
/Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent); | ||
} | ||
return window._webAnimationsUpdateSvgTransformAttr; | ||
} | ||
|
||
var styleAttributes = { | ||
cssText: 1, | ||
length: 1, | ||
|
@@ -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 && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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++) { | ||
|
@@ -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]; | ||
}, | ||
}; | ||
|
@@ -185,7 +229,9 @@ | |
} | ||
}; | ||
|
||
if (WEB_ANIMATIONS_TESTING) | ||
if (WEB_ANIMATIONS_TESTING) { | ||
testing.ensureStyleIsPatched = ensureStyleIsPatched; | ||
testing.updateSvgTransformAttr = updateSvgTransformAttr; | ||
} | ||
|
||
})(webAnimations1, webAnimationsTesting); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,14 @@ | |
(function(scope, testing) { | ||
|
||
var aliased = {}; | ||
var canonical = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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']); | ||
|
@@ -31,5 +33,8 @@ | |
scope.propertyName = function(property) { | ||
return aliased[property] || property; | ||
}; | ||
scope.canonicalPropertyName = function(property) { | ||
return canonical[property] || property; | ||
}; | ||
|
||
})(webAnimations1, webAnimationsTesting); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
|
@@ -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: ''}}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *Safari There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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". There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)'); | ||
}); | ||
}); |
There was a problem hiding this comment.
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==
.There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done