Allow transitions and animations to work within iframes

Svelte works great in iframes except for transitions and animations. This fixes that issue.

See #3624.
pull/3625/head
Jacob Wright 6 years ago
parent 5dda05213e
commit c9527f683f

@ -1,9 +1,11 @@
import { element } from './dom'; import { element } from './dom';
import { raf } from './environment'; import { raf } from './environment';
let stylesheet; type DocStyles = [CSSStyleSheet, Record<string, true>];
let activeDocs = new Set<Document>();
let docStyles = new WeakMap<Document, DocStyles>();
let active = 0; let active = 0;
let current_rules = {};
// https://github.com/darkskyapp/string-hash/blob/master/index.js // https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str: string) { function hash(str: string) {
@ -25,14 +27,18 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b:
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
const name = `__svelte_${hash(rule)}_${uid}`; const name = `__svelte_${hash(rule)}_${uid}`;
const doc = node.ownerDocument;
activeDocs.add(doc);
let [ stylesheet, current_rules ] = (docStyles.get(doc) || []) as DocStyles;
if (!stylesheet) {
current_rules = {};
const style = element('style');
doc.head.appendChild(style);
stylesheet = style.sheet as CSSStyleSheet;
docStyles.set(doc, [ stylesheet, current_rules ]);
}
if (!current_rules[name]) { if (!current_rules[name]) {
if (!stylesheet) {
const style = element('style');
document.head.appendChild(style);
stylesheet = style.sheet;
}
current_rules[name] = true; current_rules[name] = true;
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
} }
@ -53,14 +59,19 @@ export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string
) )
.join(', '); .join(', ');
if (name && !--active) clear_rules(); active = Math.max(0, active - 1);
if (name && !active) clear_rules();
} }
export function clear_rules() { export function clear_rules() {
raf(() => { raf(() => {
if (active) return; if (active) return;
let i = stylesheet.cssRules.length; activeDocs.forEach(doc => {
while (i--) stylesheet.deleteRule(i); const [ stylesheet ] = docStyles.get(doc);
current_rules = {}; let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i);
docStyles.set(doc, [ stylesheet, {} ]);
});
activeDocs.clear();
}); });
} }

@ -0,0 +1,16 @@
<script>
export let visible;
function foo() {
return {
duration: 100,
css: t => {
return `opacity: ${t}`;
}
};
}
</script>
{#if visible}
<div transition:foo></div>
{/if}

@ -0,0 +1,58 @@
<script>
import { onMount, onDestroy, tick } from 'svelte';
export let component;
let frame;
let doc;
let content;
$: mountComponent(doc, component);
$: updateProps($$props);
function mountComponent(doc) {
if (content) content.$destroy();
if (doc && component) {
const { component, ...props } = $$props;
content = new component({ target: doc.body, props });
}
}
function updateProps(props) {
if (content) {
const { component, ...rest } = props;
content.$set(rest);
}
}
function loadHandler() {
doc = frame.contentDocument;
// import styles
Array.from(document.querySelectorAll('style, link[rel="stylesheet"]'))
.forEach(node => doc.head.appendChild(node.cloneNode(true)));
}
onMount(async () => {
await tick();
if (frame.contentDocument.readyState === 'complete' && frame.contentDocument.defaultView) {
loadHandler();
} else {
frame.addEventListener('load', loadHandler);
}
});
onDestroy(() => {
if (frame) frame.removeEventListener('load', loadHandler);
if (content) content.$destroy();
});
</script>
<iframe bind:this={frame} title="frame"></iframe>
<style>
iframe {
border: none;
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,18 @@
export default {
skip_if_ssr: true,
async test({ assert, component, target, window, raf }) {
const frame = target.querySelector('iframe');
await Promise.resolve();
component.visible = true;
const div = frame.contentDocument.querySelector('div');
raf.tick(25);
component.visible = false;
raf.tick(26);
assert.ok(~div.style.animation.indexOf('25ms'));
},
};

@ -0,0 +1,8 @@
<script>
import Frame from './Frame.svelte';
import Foo from './Foo.svelte';
export let visible;
</script>
<Frame component={Foo} {visible}/>
Loading…
Cancel
Save