如何在不刷新页面的情况下启动计时器 (Rails/JavaScript)?
How to start a timer without page refresh (Rails/JavaScript)?
我有一个名为 'Deal' 的模型,它具有 start_at
和 end_at
属性。我已经使用 hotwire/stimulus
JS 实现了一个倒数计时器。
- 当交易开始时(开始日期是过去的日期,结束日期是未来的日期),将显示显示剩余交易时间的倒数计时器。例如,剩余交易时间:2 小时 4 分 30 秒,依此类推。它将减少 1 秒。
- 如果交易尚未开始(开始日期是未来),页面将显示“交易将于#{datetime} 开始”。
但是,如果交易同时开始,用户需要刷新他们当前所在的页面才能看到计时器(即从“交易将于 #{datetime} 开始”过渡到倒计时) ).我想知道在不刷新页面的情况下启动计时器的最佳方法是什么。谢谢
管理 'timer' 每 X 毫秒执行一次 运行 某些功能的方法是通过浏览器的 setInterval
function。
这个函数可以像这样使用 - const intervalID = setInterval(myCallback, 500);
- 其中 myCallback
是每 500
毫秒 运行 的函数。
计时器可以是 'cancelled',方法是调用 clearInterval
并为其提供作为 setInterval
.
结果创建的间隔 ID
示例HTML
- 这里我们有一个基本的 HTMl 结构,我们在其中设置我们的控制器
timer
并设置 from/to 时间以及保存基于三种状态的消息的目标。
- 这三种状态分别是'before'、'during'(当当前时间在两个时间之间时)和'after'.
<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 字符串,并记住 time-zones 的细微差别可能很复杂)。
- 当控制器连接时,我们做三件事; 1. 设置一个计时器到运行
this.update
每X 毫秒并将计时器ID 放在class 上以便稍后清除为this._timer
。 2. 设置时间值(消息传递的内部时间标签)。 3. 运行 this.update
方法初始时间。
this.getTimeData
解析 from/to 日期时间字符串并进行一些基本验证,它还 returns 这些日期对象以及一个 status
字符串,它将是 BEFORE
/DURING
/AFTER
.
this.update
- 此 shows/hides 基于已解决状态的相关消息部分。
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();
}
}
我有一个名为 'Deal' 的模型,它具有 start_at
和 end_at
属性。我已经使用 hotwire/stimulus
JS 实现了一个倒数计时器。
- 当交易开始时(开始日期是过去的日期,结束日期是未来的日期),将显示显示剩余交易时间的倒数计时器。例如,剩余交易时间:2 小时 4 分 30 秒,依此类推。它将减少 1 秒。
- 如果交易尚未开始(开始日期是未来),页面将显示“交易将于#{datetime} 开始”。
但是,如果交易同时开始,用户需要刷新他们当前所在的页面才能看到计时器(即从“交易将于 #{datetime} 开始”过渡到倒计时) ).我想知道在不刷新页面的情况下启动计时器的最佳方法是什么。谢谢
管理 'timer' 每 X 毫秒执行一次 运行 某些功能的方法是通过浏览器的 setInterval
function。
这个函数可以像这样使用 - const intervalID = setInterval(myCallback, 500);
- 其中 myCallback
是每 500
毫秒 运行 的函数。
计时器可以是 'cancelled',方法是调用 clearInterval
并为其提供作为 setInterval
.
示例HTML
- 这里我们有一个基本的 HTMl 结构,我们在其中设置我们的控制器
timer
并设置 from/to 时间以及保存基于三种状态的消息的目标。 - 这三种状态分别是'before'、'during'(当当前时间在两个时间之间时)和'after'.
<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 字符串,并记住 time-zones 的细微差别可能很复杂)。 - 当控制器连接时,我们做三件事; 1. 设置一个计时器到运行
this.update
每X 毫秒并将计时器ID 放在class 上以便稍后清除为this._timer
。 2. 设置时间值(消息传递的内部时间标签)。 3. 运行this.update
方法初始时间。 this.getTimeData
解析 from/to 日期时间字符串并进行一些基本验证,它还 returns 这些日期对象以及一个status
字符串,它将是BEFORE
/DURING
/AFTER
.this.update
- 此 shows/hides 基于已解决状态的相关消息部分。
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();
}
}