管理每 X 毫秒运行一些功能的“计时器”的方法是通过浏览器的setInterval function。
这个函数可以这样使用——const intervalID = setInterval(myCallback, 500);——其中myCallback是每500ms尝试运行的函数。
可以通过调用clearInterval 并为其提供作为setInterval 结果创建的间隔ID 来“取消”计时器。
示例 HTML
- 这里我们有一个基本的 HTMl 结构,我们在其中设置控制器
timer 并设置从/到时间以及基于三种状态保存消息的目标。
- 这三种状态分别是“之前”、“期间”(当前时间介于两者之间)和“之后”。
<section class="prose m-5">
<div
data-controller="timer"
data-timer-from-value="2022-03-08T10:41:32.111Z"
data-timer-to-value="2022-03-09T11:10:32.111Z"
>
<div style="display: none" data-timer-target="before">
Deal will start on <time data-timer-target="fromTime"></time>
</div>
<div style="display: none" data-timer-target="during">
Deal is active <time data-timer-target="toTimeRelative"></time>
</div>
<div style="display: none" data-timer-target="after">
Deal ended on <time data-timer-target="toTime"></time>
</div>
</div>
</section>
示例刺激控制器
- 这个
timerController 接受to 和from 时间作为字符串(最好使用ISO 字符串,记住时区的细微差别可能很复杂)。
- 当控制器连接时,我们做三件事; 1.设置一个定时器,每X毫秒运行一次
this.update,并将定时器ID放在类上,以便稍后清除为this._timer。 2. 设置时间值(消息的内部时间标签)。 3. 初始运行this.update方法。
-
this.getTimeData 解析从/到日期时间字符串并进行一些基本验证,它还返回这些日期对象以及 status 字符串,该字符串将是 BEFORE/DURING/AFTER 之一。
-
this.update - 这会根据已解决的状态显示/隐藏相关的消息部分。
import { Controller } from '@hotwired/stimulus';
const BEFORE = 'BEFORE';
const DURING = 'DURING';
const AFTER = 'AFTER';
export default class extends Controller {
static values = {
interval: { default: 500, type: Number },
locale: { default: 'en-GB', type: String },
from: String,
to: String,
};
static targets = [
'before',
'during',
'after',
'fromTime',
'toTime',
'toTimeRelative',
];
connect() {
this._timer = setInterval(() => {
this.update();
}, this.intervalValue);
this.setTimeValues();
this.update();
}
getTimeData() {
const from = this.hasFromValue && new Date(this.fromValue);
const to = this.hasToValue && new Date(this.toValue);
if (!from || !to) return;
if (from > to) {
throw new Error('From time must be after to time.');
}
const now = new Date();
const status = (() => {
if (now < from) return BEFORE;
if (now >= from && now <= to) return DURING;
return AFTER;
})();
return { from, to, now, status };
}
setTimeValues() {
const { from, to, now } = this.getTimeData();
const locale = this.localeValue;
const formatter = new Intl.DateTimeFormat(locale, {
dateStyle: 'short',
timeStyle: 'short',
});
this.fromTimeTargets.forEach((element) => {
element.setAttribute('datetime', from);
element.innerText = formatter.format(from);
});
this.toTimeTargets.forEach((element) => {
element.setAttribute('datetime', to);
element.innerText = formatter.format(to);
});
const relativeFormatter = new Intl.RelativeTimeFormat(locale, {
numeric: 'auto',
});
this.toTimeRelativeTargets.forEach((element) => {
element.setAttribute('datetime', to);
element.innerText = relativeFormatter.format(
Math.round((to - now) / 1000),
'seconds'
);
});
}
update() {
const { status } = this.getTimeData();
[
[BEFORE, this.beforeTarget],
[DURING, this.duringTarget],
[AFTER, this.afterTarget],
].forEach(([key, element]) => {
if (key === status) {
element.style.removeProperty('display');
} else {
element.style.setProperty('display', 'none');
}
});
this.setTimeValues();
if (status === AFTER) {
this.stopTimer();
}
}
stopTimer() {
const timer = this._timer;
if (!timer) return;
clearInterval(timer);
}
disconnect() {
// ensure we clean up so the timer is not running if the element gets removed
this.stopTimer();
}
}