无法设置自定义内置元素的 textContent,超时除外
Can't set textContent of a customised built-in element except in timeout
我正在制作一个自动本地化其视觉文本表示的自定义元素:
class LocalDate extends HTMLTimeElement {
// Specify observed attributes so that
// attributeChangedCallback will work
static get observedAttributes() {
return ["datetime"];
}
constructor() {
// Always call super first in constructor
const self = super();
this.formatter = new Intl.DateTimeFormat(navigator.languages, {
year: "numeric",
month: "short",
day: "numeric"
});
return self;
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "datetime") {
this.textContent = "";
const dateMiliseconds = Date.parse(newValue);
if (!Number.isNaN(dateMiliseconds)) {
const dateString = this.formatter.format(new Date(dateMiliseconds));
this.textContent = dateString;
}
}
}
}
customElements.define('local-date', LocalDate, {
extends: "time"
});
<time is="local-date" datetime="2022-01-13T07:13:00+10:00">13 Jan 2022 - Still here</time>
问题在于脚本标记恰好是 运行 - 如果在解析正文后它是 运行,那么它会按预期工作。否则,元素不会显示为日期,而是显示日期字符串 除了 元素中已有的文本。
JsFiddle 和 Whosebug 都把 script 标签放在 body 的底部,所以错误只能用一个 DataUrl 才能看到:
数据:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%3E%0D%0A%3Chead%3E%0D%0A%3Cmeta% 20charset%3D%22utf-8%22%2F%3E%0D%0A%3Ctitle%3ETime%20since%3C%2Ftitle%3E%0D%0A%3Cscript%3E%0D%0A%09class%20LocalDate%20extends%20HTMLTimeElement% 20%7B%0D%0A%09%09%2F%2F%20Specify%20observed%20attributes%20so%20that%0D%0A%09%09%2F%2F%20attributeChangedCallback%20will%20work%0D%0A%09% 09static%20get%20observedAttributes%28%29%20%7B%0D%0A%09%09%09return%20%5B%22datetime%22%5D%3B%0D%0A%09%09%7D%09%0D% 0A%0D%0A%09%09构造函数%28%29%20%7B%0D%0A%09%09%09%2F%2F%20Always%20call%20super%20first%20in%20构造函数%0D%0A%09% 09%09const%20self%20%3D%20super%28%29%3B%0D%0A%0D%0A%09%09%09this.formatter%20%3D%20new%20Intl.DateTimeFormat%28navigator.languages%2C%20%7B%20year%3A%20%22numeric%22%2C%20month%3A%20%22short%22%2C%20day%3A%20%22numeric%22%20%7D%29% 3B%0D%0A%0D%0A%09%09%09return%20self%3B%0D%0A%09%09%7D%0D%0A%0D%0A%09%09attributeChangedCallback%28name%2C%20oldValue%2C% 20newValue%29%20%7B%0D%0A%09%09%09if%20%28name%20%3D%3D%3D%20%22date时间%22%29%20%7B%0D%0A%09%09%09%09this.textContent%20%3D%20%22%22%3B%0D%0A%09%09%09%09const% 20dateMiliseconds%20%3D%20Date.parse%28newValue%29%3B%0D%0A%09%09%09%09if%20%28%21Number.isNaN%28dateMiliseconds%29%29%20%7B% 0D%0A%09%09%09%09%09const%20dateString%20%3D%20this.formatter.format%28new%20Date%28dateMiliseconds%29%29%3B%0D%0A%09%09%09%09% 09%2F%2F%20Bizarrly%2C%20this%20doesn%27t%20seem%20to%20work%20without%20doing%20this%20in%20a%20timeout%3F%21%3F%21%0D%0A%09%09% 09%09%09this.textContent%20%3D%20dateString%3B%0D%0A%09%09%09%09%7D%0D%0A%09%09%09%7D%0D%0A%09% 09%7D%0D%0A%09%7D%0D%0A%09%0D%0A%09customElements.define%28%27local-date%27%2C%20LocalDate%2C%20%7B%20extends%3A% 20%22time%22%20%7D%29%3B%0D%0A%3C%2Fscript%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%3E%0D%0A%3Cp%3ELast% 20updated%20%3Ctime%20is%3D%22local-date%22%20datetime%3D%222022-01-13T07%3A13%3A00%2B10%3A00%22%3E13%20Jan%202022%20-%20Still%20here%3C %2Ftime%3E%3C%2Fp%3E%0D%0A%3C%2Fbody%3E
我已经在 Firefox 和 Chrome 中复制了这个 - 知道这里发生了什么吗?
您的问题出现了,
因为 attributeChangedCallback
和 connectedCallback
都在 开始标记
上触发
(并按此顺序!)
所以
- 当自定义元素被 定义 BEFORE 用于 DOM,
attributeChangedCallback
在 opening 标签上触发,
- 添加你的
.textContent
- after 您的自定义元素中的 lightDOM parsed
- 默认情况下添加到现有内容
这就是为什么您会在下面的示例中看到 lightDOM #2
<style>
time {
display: block
}
</style>
<time id=BEFORE is="local-date" datetime="2022-01-13T07:13:00+10:00"> lightDOM #1
<script>console.log("time element BEFORE parsed")</script>
</time>
<script>
customElements.define('local-date', class extends HTMLTimeElement {
static get observedAttributes() {
return ["datetime"];
}
attributeChangedCallback(name, oldValue, newValue) {
console.warn("attributeChangedCallback", this.id, this.isConnected);
this.textContent = (new Intl.DateTimeFormat(navigator.languages, {
year: "numeric",
month: "short",
day: "numeric"
})).format(new Date(Date.parse(newValue)));
}
}, {
extends: "time"
});
console.warn("Custom Element: local-date defined");
</script>
<time id=AFTER is="local-date" datetime="2022-01-13T07:13:00+10:00"> lightDOM #2
<script>console.log("time element AFTER parsed")</script>
</time>
不要在attributeChangedCallback
中进行初始化
所以我们必须确保只在 解析所有 DOM 之后才渲染时间,使用 setTimeout
另见:
and..(但在解决方案中没有任何作用)因为 Apple 自 2016 年以来就表示他们永远不会实施 Customized Built-In Elements,使它是一个 自治 元素 <local-date>
您还可以再添加 N 千字节并使用 the 58 Tools for building Web Components 中的任何一个,有些会保护您免受这种低级行为的影响。 但是拥有工具的傻瓜仍然是傻瓜。
回调执行时的良好参考:https://andyogo.github.io/custom-element-reactions-diagram/
但是 attributeChangedCallback
在 connectedCallback
!
之前 运行
请注意如何使用 this.isConnected
<style>
local-date {
display: block
}
</style>
<local-date id=BEFORE datetime="2022-01-13T07:13:00+10:00"> lightDOM #1
<script>console.log("time element BEFORE parsed")</script>
</local-date>
<script>
customElements.define('local-date', class extends HTMLElement {
static get observedAttributes() {
return ["datetime"];
}
attributeChangedCallback(name, oldValue, newValue) {
console.warn("attributeChangedCallback", this.id, this.isConnected);
if (oldValue) this.renderTime(newValue);
}
connectedCallback(){
console.warn("connectedCallback", this.id);
setTimeout(()=>this.renderTime());
}
renderTime(dt=this.getAttribute("datetime")){
console.warn("renderTime", this.id);
this.textContent = (new Intl.DateTimeFormat(navigator.languages, {
year: "numeric",
month: "short",
day: "numeric"
})).format(new Date(Date.parse(dt)));
}
});
console.warn("Custom Element: local-date defined");
</script>
<local-date id=AFTER datetime="2022-01-13T07:13:00+10:00"> lightDOM #2
<script>console.log("time element AFTER parsed")</script>
</local-date>
我正在制作一个自动本地化其视觉文本表示的自定义元素:
class LocalDate extends HTMLTimeElement {
// Specify observed attributes so that
// attributeChangedCallback will work
static get observedAttributes() {
return ["datetime"];
}
constructor() {
// Always call super first in constructor
const self = super();
this.formatter = new Intl.DateTimeFormat(navigator.languages, {
year: "numeric",
month: "short",
day: "numeric"
});
return self;
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "datetime") {
this.textContent = "";
const dateMiliseconds = Date.parse(newValue);
if (!Number.isNaN(dateMiliseconds)) {
const dateString = this.formatter.format(new Date(dateMiliseconds));
this.textContent = dateString;
}
}
}
}
customElements.define('local-date', LocalDate, {
extends: "time"
});
<time is="local-date" datetime="2022-01-13T07:13:00+10:00">13 Jan 2022 - Still here</time>
问题在于脚本标记恰好是 运行 - 如果在解析正文后它是 运行,那么它会按预期工作。否则,元素不会显示为日期,而是显示日期字符串 除了 元素中已有的文本。
JsFiddle 和 Whosebug 都把 script 标签放在 body 的底部,所以错误只能用一个 DataUrl 才能看到:
数据:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%3E%0D%0A%3Chead%3E%0D%0A%3Cmeta% 20charset%3D%22utf-8%22%2F%3E%0D%0A%3Ctitle%3ETime%20since%3C%2Ftitle%3E%0D%0A%3Cscript%3E%0D%0A%09class%20LocalDate%20extends%20HTMLTimeElement% 20%7B%0D%0A%09%09%2F%2F%20Specify%20observed%20attributes%20so%20that%0D%0A%09%09%2F%2F%20attributeChangedCallback%20will%20work%0D%0A%09% 09static%20get%20observedAttributes%28%29%20%7B%0D%0A%09%09%09return%20%5B%22datetime%22%5D%3B%0D%0A%09%09%7D%09%0D% 0A%0D%0A%09%09构造函数%28%29%20%7B%0D%0A%09%09%09%2F%2F%20Always%20call%20super%20first%20in%20构造函数%0D%0A%09% 09%09const%20self%20%3D%20super%28%29%3B%0D%0A%0D%0A%09%09%09this.formatter%20%3D%20new%20Intl.DateTimeFormat%28navigator.languages%2C%20%7B%20year%3A%20%22numeric%22%2C%20month%3A%20%22short%22%2C%20day%3A%20%22numeric%22%20%7D%29% 3B%0D%0A%0D%0A%09%09%09return%20self%3B%0D%0A%09%09%7D%0D%0A%0D%0A%09%09attributeChangedCallback%28name%2C%20oldValue%2C% 20newValue%29%20%7B%0D%0A%09%09%09if%20%28name%20%3D%3D%3D%20%22date时间%22%29%20%7B%0D%0A%09%09%09%09this.textContent%20%3D%20%22%22%3B%0D%0A%09%09%09%09const% 20dateMiliseconds%20%3D%20Date.parse%28newValue%29%3B%0D%0A%09%09%09%09if%20%28%21Number.isNaN%28dateMiliseconds%29%29%20%7B% 0D%0A%09%09%09%09%09const%20dateString%20%3D%20this.formatter.format%28new%20Date%28dateMiliseconds%29%29%3B%0D%0A%09%09%09%09% 09%2F%2F%20Bizarrly%2C%20this%20doesn%27t%20seem%20to%20work%20without%20doing%20this%20in%20a%20timeout%3F%21%3F%21%0D%0A%09%09% 09%09%09this.textContent%20%3D%20dateString%3B%0D%0A%09%09%09%09%7D%0D%0A%09%09%09%7D%0D%0A%09% 09%7D%0D%0A%09%7D%0D%0A%09%0D%0A%09customElements.define%28%27local-date%27%2C%20LocalDate%2C%20%7B%20extends%3A% 20%22time%22%20%7D%29%3B%0D%0A%3C%2Fscript%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%3E%0D%0A%3Cp%3ELast% 20updated%20%3Ctime%20is%3D%22local-date%22%20datetime%3D%222022-01-13T07%3A13%3A00%2B10%3A00%22%3E13%20Jan%202022%20-%20Still%20here%3C %2Ftime%3E%3C%2Fp%3E%0D%0A%3C%2Fbody%3E
我已经在 Firefox 和 Chrome 中复制了这个 - 知道这里发生了什么吗?
您的问题出现了,
因为 attributeChangedCallback
和 connectedCallback
都在 开始标记
上触发
(并按此顺序!)
所以
- 当自定义元素被 定义 BEFORE 用于 DOM,
attributeChangedCallback
在 opening 标签上触发,- 添加你的
.textContent
- after 您的自定义元素中的 lightDOM parsed
- 默认情况下添加到现有内容
这就是为什么您会在下面的示例中看到 lightDOM #2
<style>
time {
display: block
}
</style>
<time id=BEFORE is="local-date" datetime="2022-01-13T07:13:00+10:00"> lightDOM #1
<script>console.log("time element BEFORE parsed")</script>
</time>
<script>
customElements.define('local-date', class extends HTMLTimeElement {
static get observedAttributes() {
return ["datetime"];
}
attributeChangedCallback(name, oldValue, newValue) {
console.warn("attributeChangedCallback", this.id, this.isConnected);
this.textContent = (new Intl.DateTimeFormat(navigator.languages, {
year: "numeric",
month: "short",
day: "numeric"
})).format(new Date(Date.parse(newValue)));
}
}, {
extends: "time"
});
console.warn("Custom Element: local-date defined");
</script>
<time id=AFTER is="local-date" datetime="2022-01-13T07:13:00+10:00"> lightDOM #2
<script>console.log("time element AFTER parsed")</script>
</time>
不要在attributeChangedCallback
中进行初始化
所以我们必须确保只在 解析所有 DOM 之后才渲染时间,使用
setTimeout
另见:and..(但在解决方案中没有任何作用)因为 Apple 自 2016 年以来就表示他们永远不会实施 Customized Built-In Elements,使它是一个 自治 元素
<local-date>
您还可以再添加 N 千字节并使用 the 58 Tools for building Web Components 中的任何一个,有些会保护您免受这种低级行为的影响。 但是拥有工具的傻瓜仍然是傻瓜。
回调执行时的良好参考:https://andyogo.github.io/custom-element-reactions-diagram/
但是
attributeChangedCallback
在connectedCallback
!
之前 运行 请注意如何使用this.isConnected
<style>
local-date {
display: block
}
</style>
<local-date id=BEFORE datetime="2022-01-13T07:13:00+10:00"> lightDOM #1
<script>console.log("time element BEFORE parsed")</script>
</local-date>
<script>
customElements.define('local-date', class extends HTMLElement {
static get observedAttributes() {
return ["datetime"];
}
attributeChangedCallback(name, oldValue, newValue) {
console.warn("attributeChangedCallback", this.id, this.isConnected);
if (oldValue) this.renderTime(newValue);
}
connectedCallback(){
console.warn("connectedCallback", this.id);
setTimeout(()=>this.renderTime());
}
renderTime(dt=this.getAttribute("datetime")){
console.warn("renderTime", this.id);
this.textContent = (new Intl.DateTimeFormat(navigator.languages, {
year: "numeric",
month: "short",
day: "numeric"
})).format(new Date(Date.parse(dt)));
}
});
console.warn("Custom Element: local-date defined");
</script>
<local-date id=AFTER datetime="2022-01-13T07:13:00+10:00"> lightDOM #2
<script>console.log("time element AFTER parsed")</script>
</local-date>