In a project I'm working on, I have this LitElement component, which is absolute-positioned and determines its left or top locations according to its reactive properties.
I've encountered a problem in Safari only, including iOS Safari/Chrome/Firefox. The element has updated its shadow styles, but in the view it does not move at all. I realized its a render issue when I found that when the cursor hovers the element, or exits the browser view, the element pops to the expected location.
I managed to reproduce the problem with a simpler code:
my-elem.ts:
import { LitElement, html, property, customElement, css } from 'lit-element';
@customElement('my-elem')
export class MyElem extends LitElement {
@property({ type: Number, reflect: true })
left: number = 30
static get styles() {
return css`
:host { position: absolute; }
div { position: absolute; }
`;
}
render() {
return html`
<style>
:host { left: ${this.left}px; }
</style>
<div> Hello </div>
`;
}
}
index.html:
<html lang="en">
<head>
<script src="my-elem.ts"></script>
</head>
<body>
<my-elem id="my-elem" left="50"></my-elem>
<button id="move-btn">move</button>
<script>
const elem = document.getElementById('ma-elem');
const moveBtn = document.getElementById('move-btn');
moveBtn.onclick = function() {
elem.left += 30;
}
</script>
</body>
</html>
I found that this happens only on :host
selector. If the shadowed styling updates the div
's left
, it renders without any problems.
I wish to avoid forcing the browser to layout/paint, due to performance issues.
I think the root of your problem is that you are trying to use an expression inside a style element in your render template.
The LitElement guide mentions that using them that way has limitations and can cause performance issues. I think your problem is just facing one of them so you should remove that style element.
As an alternative, since you want to affect the host element, you can actually do this in an easier way if you just don't use a property but style the host directly.
So when using my-elem
the code would look like:
<my-elem style="left: 30px;"></my-elem>
This is because styles applied to :host
in shadow DOM have less priority than those applied to it using classes or the style attribute by its parent.
Alternatively, if you really want to keep the property, you can create property accessors for the left
property and set the style to the host from there like this:
export class MyElem extends LitElement {
// other code
set left(value) {
const oldValue = this.left;
this._left = value;
this.style.setProperty('left', `${value}px`)
this.requestUpdate('left', oldValue);
}
get left() {
return this._left;
}
}