Skip to content

Commit

Permalink
fix(animations): allow animations in shadow DOM
Browse files Browse the repository at this point in the history
enhanced AnimationDriver's containsElement method to not only
use the native node contains-method but to walk up the DOM tree
by also considering shadow elements. Fixing this allows animations
to be played if they are placed inside a shadow DOM.

Closes angular#25672
  • Loading branch information
jeripeierSBB committed Dec 17, 2020
1 parent 0fc8466 commit bfe9555
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 7 deletions.
2 changes: 1 addition & 1 deletion goldens/size-tracking/aio-payloads.json
Expand Up @@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3153,
"main-es2015": 431137,
"main-es2015": 431684,
"polyfills-es2015": 52493
}
}
Expand Down
Expand Up @@ -20,7 +20,6 @@ const TAB_SPACE = ' ';

export class CssKeyframesDriver implements AnimationDriver {
private _count = 0;
private readonly _head: any = document.querySelector('head');

validateStyleProperty(prop: string): boolean {
return validateStyleProperty(prop);
Expand Down Expand Up @@ -107,7 +106,8 @@ export class CssKeyframesDriver implements AnimationDriver {

const animationName = `${KEYFRAMES_NAME_PREFIX}${this._count++}`;
const kfElm = this.buildKeyframeElement(element, animationName, keyframes);
document.querySelector('head')!.appendChild(kfElm);
const nodeToAppendKfElm = findNodeToAppendKfElm(element);
nodeToAppendKfElm.appendChild(kfElm);

const specialStyles = packageNonAnimatableStyles(element, keyframes);
const player = new CssKeyframesPlayer(
Expand All @@ -118,6 +118,20 @@ export class CssKeyframesDriver implements AnimationDriver {
}
}

function findNodeToAppendKfElm(element: any): Node {
let current = element;
let shadowRoot = null;
while (current && current !== document.documentElement) {
if (current.shadowRoot) {
shadowRoot = current.shadowRoot;
break;
}
current = current.parentNode || current.host;
}

return shadowRoot || document.querySelector('head')!;
}

function flattenKeyframesIntoStyles(keyframes: null|{[key: string]: any}|
{[key: string]: any}[]): {[key: string]: any} {
let flatKeyframes: {[key: string]: any} = {};
Expand Down
13 changes: 12 additions & 1 deletion packages/animations/browser/src/render/shared.ts
Expand Up @@ -162,7 +162,18 @@ const _isNode = isNode();
if (_isNode || typeof Element !== 'undefined') {
// this is well supported in all browsers
_contains = (elm1: any, elm2: any) => {
return elm1.contains(elm2) as boolean;
if (!isBrowser()) {
return elm1.contains(elm2) as boolean;
}
// walk up DOM tree
let current = elm2;
while (current && current !== document.documentElement) {
if (current === elm1) {
return true;
}
current = current.parentNode || current.host; // consider host to support shadow DOM
}
return false;
};

_matches = (() => {
Expand Down
Expand Up @@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks} from '@angular/core/testing';

import {CssKeyframesDriver} from '../../../src/render/css_keyframes/css_keyframes_driver';
import {CssKeyframesPlayer} from '../../../src/render/css_keyframes/css_keyframes_player';
Expand Down Expand Up @@ -104,7 +104,7 @@ describe('CssKeyframesDriver tests', () => {
expect(easing).toEqual('ease-out');
});

it('should animate until the `animationend` method is emitted, but stil retain the <style> method and the element animation details',
it('should animate until the `animationend` method is emitted, but still retain the <style> method and the element animation details',
fakeAsync(() => {
// IE11 cannot create an instanceof AnimationEvent
if (!supportsAnimationEventCreation()) return;
Expand Down Expand Up @@ -144,7 +144,7 @@ describe('CssKeyframesDriver tests', () => {
assertElementExistsInDom(matchingStyleElm, true);
}));

it('should animate until finish() is called, but stil retain the <style> method and the element animation details',
it('should animate until finish() is called, but still retain the <style> method and the element animation details',
fakeAsync(() => {
const elm = createElement();
const animator = new CssKeyframesDriver();
Expand Down Expand Up @@ -369,6 +369,32 @@ describe('CssKeyframesDriver tests', () => {
expect(k2).toEqual({width: '400px', height: '400px', offset: 0.5});
expect(k3).toEqual({width: '500px', height: '500px', offset: 1});
});

if (supportsShadowDOM()) {
it('should append <style> in shadow DOM root element', fakeAsync(() => {
const hostElement = createElement();
const shadowRoot = hostElement.attachShadow({mode: 'open'});
const elementToAnimate = createElement();
shadowRoot.appendChild(elementToAnimate);
const animator = new CssKeyframesDriver();

assertExistingAnimationDuration(elementToAnimate, 0);
expect(shadowRoot.querySelector('style')).toBeFalsy();

const player = animator.animate(
elementToAnimate,
[
{width: '0px', offset: 0},
{width: '200px', offset: 1},
],
1234, 0, 'ease-out');

player.play();

assertExistingAnimationDuration(elementToAnimate, 1234);
assertElementExistsInDom(shadowRoot.querySelector('style'), true);
}));
}
});
});

Expand Down Expand Up @@ -396,3 +422,7 @@ function isIE() {
// note that this only applies to older IEs (not edge)
return (window as any).document['documentMode'] ? true : false;
}

function supportsShadowDOM(): boolean {
return typeof (<any>document.body).attachShadow !== 'undefined';
}
Expand Up @@ -49,6 +49,20 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
expect(player instanceof WebAnimationsPlayer).toBeTruthy();
});
});

if (supportsShadowDOM()) {
describe('when animation is inside a shadow DOM', () => {
it('should the driver determine that body contains element to animate', (() => {
const hostElement = createElement();
const shadowRoot = hostElement.attachShadow({mode: 'open'});
const elementToAnimate = createElement();
shadowRoot.appendChild(elementToAnimate);
document.body.appendChild(hostElement);
const animator = new WebAnimationsDriver();
expect(animator.containsElement(document.body, elementToAnimate)).toBeTrue();
}));
});
}
});
}

Expand All @@ -59,3 +73,7 @@ function makeDriver() {
function createElement() {
return document.createElement('div');
}

function supportsShadowDOM(): boolean {
return typeof (<any>document.body).attachShadow !== 'undefined';
}

0 comments on commit bfe9555

Please sign in to comment.