Skip to content
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

Transitions on WebComponents not working #4735

Closed
msaglietto opened this issue Apr 27, 2020 · 14 comments
Closed

Transitions on WebComponents not working #4735

msaglietto opened this issue Apr 27, 2020 · 14 comments

Comments

@msaglietto
Copy link

Describe the bug
When you define a transition on an element that is a customElement (web component) the transitions animations dont show since the css for the animation are not applied

Logs
It fails silently

To Reproduce
Check repo but is just the transition example made web component
https://github.com/msaglietto/svelte-transitions-issue
Or just

<script>
  import { fade } from 'svelte/transition';
  let visible = true;
</script>

<svelte:options tag="test-transitions" />

<label>
  <input type="checkbox" bind:checked={visible}>
  visible
</label>

{#if visible}
  <p transition:fade="{{ duration: 3000 }}">
    Fades in and out not working
  </p>
{/if}

Expected behavior
Transitions working fine in web compoent with the shadow root restrictions

Information about your Svelte project:

  • Your browser and the version: Chrome 81.0.4044.103

  • Your operating system: Chrome OS Version 81.0.4044.10

  • Svelte version: 3.21

  • Whether your project uses Webpack or Rollup: Rollup

Severity
It was blocking us from using svelte but we found a work around

Additional context
The issue happen because svelte insert the css in the head of the ownerDocument:
https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/style_manager.ts#L32
In the case of web component the elementes inside of the shadow-root can not read outside of it so the head css dont apply

I think maybe it could be solved if you can configure where svelte insert the css

A quite ugly workaround is to redefine node ownerDocument and its head element on the transition

  function workaround(node, params) {
     if (!node.hasOwnProperty('ownerDocument')) {
        Object.defineProperty(node, 'ownerDocument', { get: function() { return node.parentElement; } });
        node.parentElement.head = node.parentElement
     }
    return fly(node, params)
  }
@Conduitry
Copy link
Member

Sounds like a duplicate of #1825.

@msaglietto
Copy link
Author

You are right my bad I serached as Web Component not Custom Element =S
Well maybe the workaround helps someone

@Samuel-Martineau
Copy link

Thanks for your workaround!

@crisward
Copy link

crisward commented Oct 3, 2020

The ugly workaround can be made a little bit more reusable with this. I lookup the top parent element with while loop as the above fix doesn't always work with nested transitions.

// transfix.js
export default function fix(transtion) {
  return function(node, params){
    if (!node.hasOwnProperty('ownerDocument')) {
      Object.defineProperty(node, 'ownerDocument', { get: function() { return node.parentElement; } });
      let elem = node
      while(elem.parentElement){ elem = elem.parentElement }
      node.parentElement.head = elem
    }
    return transtion(node, params)
  }
}

Then your transition can be applied with. This should work with any transition.

<script>
import { fly } from 'svelte/transition';
import fix from './transfix.js'
</script>

{#if visible}
    <p transition:fix(fly)="{{ duration: 3000 }}">
      Fly in and out working
    </p>
{/if}

@cie
Copy link

cie commented Oct 12, 2020

Thanks, this helped! I needed a slightly modified version though:

// transfix.js
export default function fix(transtion) {
  return function(node, params){
    Object.defineProperty(node, 'ownerDocument', { get: function() { return {head: node.parentNode}; } });
    return transtion(node, params)
  }
}

@yannkost
Copy link

Here to get a fix for the following transitions in typescript: fade, scale, blur, fly, slide (I only tested fade...)
Still this has to be fixed.

import type { fade, fly, scale, slide } from "svelte/types/runtime/transition";

declare type EasingFunction = (t: number) => number;

interface FadeParams {
  delay?: number;
  duration?: number;
  easing?: EasingFunction;
}

interface BlurParams {
	delay?: number;
	duration?: number;
	easing?: EasingFunction;
	amount?: number;
	opacity?: number;
}

interface FadeParams {
	delay?: number;
	duration?: number;
	easing?: EasingFunction;
}

interface FlyParams {
	delay?: number;
	duration?: number;
	easing?: EasingFunction;
	x?: number;
	y?: number;
	opacity?: number;
}

interface SlideParams {
	delay?: number;
	duration?: number;
	easing?: EasingFunction;
}

interface ScaleParams {
	delay?: number;
	duration?: number;
	easing?: EasingFunction;
	start?: number;
	opacity?: number;
}

export type TransitionFunctions = typeof fade | typeof scale | typeof blur | typeof fly | typeof slide 
export type TransitionParams = FadeParams  | ScaleParams | SlideParams | FlyParams | BlurParams
 
export default function fix(transtion:TransitionFunctions) {
	return function(node:Element, params: TransitionParams){
	  Object.defineProperty(node, 'ownerDocument', { get: function() { return {head: node.parentNode}; } });
	  return transtion(node, params) 
	}
}



transition:fix(fade)={{duration:200}}

@OClement
Copy link

@yannkost
Thanks for this snippet
We're considering using svelte to build our in-house component library and I'm working on a (very) small PoC of building Custom Elements with it.
Since I'm just getting started with Svelte, I'm not sure how to use that workaround. I'm trying to implement a button with a ripple effect (à la Material) and the animation, using tweened isn't working at this point; I'm not sure if this workaround applies?

Would it be possible for you (or anyone, really) to quickly explain how to use this "fix"?''

Thanks in advance!

@sveltejs sveltejs deleted a comment from Derar1986 Jan 24, 2021
@msaglietto
Copy link
Author

@OClement
The problem of using custom elements is that the css of the tweened animation is applied to the document header .. since for web components the outside css doent apply the animation is not played
What this workaround is doing is patching the node.ownerDocument to instead of return the document from the global scope to return the parent node of the element ... so the css is inserted in the parent node that is sill inside the web component

So if you want to use it on your button component .. you will have to apply the workaround when you mount the button or use the fixed transitions from the examples .. but you will have to make sure that the "workaround" code return an element that is still inside the web component

hope this help

@ivanhofer
Copy link
Contributor

I created a PR that makes it possible to use svelte inside a shadow dom. All styles and animations will work like in a normal svelte-application. You could install svelte from this branch to use it until it gets merged.

@OClement
Copy link

@msaglietto

Great explanation, this clarifies a lot, thanks a bunch!

@yannkost
Copy link

@OClement Just so you know how to use the fix, personnally I put the definitions/interfaces in a fix.ts file, import the fix function where needed, then i'm applying the fix when I use a Svelte transition the following way:
<div transition:fix(slide)={{ duration: 200 }}>
ESLint will still give you an error, but it still works for as far as I know.

@mahdimaiche
Copy link

mahdimaiche commented Feb 26, 2021

@yannkost your fix works but try using a transition on hover for example on a node that is not a direct child of a webcomponent (it has another parent which will be then it's ownerDocument). It will keep on creating style tags and appending them under this component each time you hover.
@crisward fix works better.

@lights0123
Copy link

An alternative method that doesn't require modification of components:

interface ExtendedDoc extends Document {
  __svelte_stylesheet: StyleShim;
}
class StyleShim {
  cssRules: string[] = [];
  private _stylesheets: CSSStyleSheet[] = [];
  constructor() {
    this.register(
      document.head.appendChild(document.createElement("style")).sheet
    );
  }
  insertRule(rule: string, index = 0) {
    this.cssRules.splice(index, 0, rule);
    for (const sheet of this._stylesheets) {
      sheet.insertRule(rule, index);
    }
  }
  deleteRule(index: number) {
    this.cssRules.splice(index, 1);
    for (const sheet of this._stylesheets) {
      sheet.deleteRule(index);
    }
  }
  register(sheet: CSSStyleSheet) {
    this._stylesheets.push(sheet);
  }
  unregister(sheet: CSSStyleSheet) {
    const i = this._stylesheets.findIndex((s) => s === sheet);
    if (i !== -1) this._stylesheets.splice(i, 1);
  }
}
const shim = new StyleShim();
(document as ExtendedDoc).__svelte_stylesheet = shim;
export default shim;

Then, bind:this on any element in your component, insert a new stylesheet and register it onMount:

const styleSheet = element.parentNode.appendChild(
  document.createElement("style")
).sheet;
StyleShim.register(styleSheet);
return () => StyleShim.unregister(styleSheet);

@Wambosa
Copy link

Wambosa commented Jul 22, 2022

necro post
The above didn't work for me, but I was able to get it working on svelte 3.48.0 with:

export default function fix(transtion:TransitionFunctions) {
  return function(node:Element, params: TransitionParams) {
    node.getRootNode = () => node.parentElement
    return transtion(node, params)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests