文档 / Web Component

嵌入计时器

播放器与编辑器都是标准 Web Component。无需任何框架即可把播放器嵌入任意页面。

  • 无需构建
  • 不发送数据
  • 用 CSS 变量定制主题

运行一个流程

复制完整 HTML 示例即可使用。托管的浏览器模块已包含依赖,无需安装包或构建工具。设置 sequence 属性,由用户点击“开始”,以便浏览器允许播放声音。

播放器底部默认显示一行“由时序计时器提供支持”的小链接,在新标签页打开本站。它是普通链接,点击前不发出任何请求。给元素加上 no-attribution 属性即可隐藏。

player-example.html打开示例
<!doctype html>
<html lang="zh-Hans">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="robots" content="noindex">
  <title>我的计时器</title>
  <style>
    body { margin: 0 auto; padding: 1rem; max-width: 48rem; font: 16px system-ui, sans-serif; }
    sequence-timer-player, sequence-timer-builder { display: block; margin: 1rem 0; }
  </style>
</head>
<body>
  <h1>我的计时器</h1>
  <sequence-timer-player locale="zh-CN"></sequence-timer-player>
  <p data-status role="status"></p>
  <script type="module">
    import 'https://timer.eigentime.org/embed/v1/player.js';
    const sequence = {
      schemaVersion: 1,
      id: 'my-timer',
      metadata: { name: { 'zh-CN': '我的计时器' } },
      children: [
        { type: 'phase', id: 'prepare', role: 'prepare', durationMs: 5000 },
        {
          type: 'group', id: 'round', repeat: 4,
          children: [
            {
              type: 'phase', id: 'work', role: 'work', durationMs: 30000,
              cues: { start: 'beep', countdownSeconds: 3 },
            },
            { type: 'phase', id: 'rest', role: 'rest', durationMs: 10000 },
          ],
        },
      ],
    };
    const player = document.querySelector('sequence-timer-player');
    player.sequence = sequence;
    player.addEventListener('complete', () => {
      document.querySelector('[data-status]').textContent = '流程已完成。';
    });
    // Playback starts only when the visitor presses Start.
  </script>
</body>
</html>

编辑器与播放器

编辑器和播放器分别加载。编辑器的 start 事件提供已通过校验的流程,示例将其交给同一个播放器执行。

builder-example.html打开示例
<!doctype html>
<html lang="zh-Hans">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="robots" content="noindex">
  <title>我的计时器</title>
  <style>
    body { margin: 0 auto; padding: 1rem; max-width: 48rem; font: 16px system-ui, sans-serif; }
    sequence-timer-player, sequence-timer-builder { display: block; margin: 1rem 0; }
  </style>
</head>
<body>
  <h1>我的计时器</h1>
  <sequence-timer-builder locale="zh-CN"></sequence-timer-builder>
  <sequence-timer-player locale="zh-CN"></sequence-timer-player>
  <p data-status role="status"></p>
  <script type="module">
    import 'https://timer.eigentime.org/embed/v1/player.js';
    import 'https://timer.eigentime.org/embed/v1/builder.js';
    const sequence = {
      schemaVersion: 1,
      id: 'my-timer',
      metadata: { name: { 'zh-CN': '我的计时器' } },
      children: [
        { type: 'phase', id: 'prepare', role: 'prepare', durationMs: 5000 },
        {
          type: 'group', id: 'round', repeat: 4,
          children: [
            {
              type: 'phase', id: 'work', role: 'work', durationMs: 30000,
              cues: { start: 'beep', countdownSeconds: 3 },
            },
            { type: 'phase', id: 'rest', role: 'rest', durationMs: 10000 },
          ],
        },
      ],
    };
    const player = document.querySelector('sequence-timer-player');
    player.sequence = sequence;
    player.addEventListener('complete', () => {
      document.querySelector('[data-status]').textContent = '流程已完成。';
    });
    const builder = document.querySelector('sequence-timer-builder');
    builder.sequence = sequence;
    builder.addEventListener('start', async (event) => {
      player.sequence = event.detail.sequence;
      await player.updateComplete;
      player.start();
      player.scrollIntoView({ block: 'start' });
    });
    // Playback starts only when the visitor presses Start.
  </script>
</body>
</html>

事件

监听 statechange、phasechange、cue 与 complete 事件来驱动你自己的界面或统计——组件本身不会向任何地方发送数据。

events.js
const player = document.querySelector('sequence-timer-player');

player.addEventListener('statechange', (event) => {
  // 'idle' | 'running' | 'paused' | 'completed'
  console.log(event.detail.state);
});

player.addEventListener('phasechange', (event) => {
  const { step } = event.detail;
  console.log(step.phase.role, step.durationMs);
});

player.addEventListener('cue', (event) => {
  console.log(event.detail.sound, event.detail.secondsRemaining);
});

player.addEventListener('complete', () => {
  console.log('done');
});

主题

颜色与尺寸通过 CSS 自定义属性暴露(--sequence-primary、--sequence-work 等),无需触碰 Shadow DOM 即可定制主题。--sequence-primary 与 --sequence-on-primary 请成对设置,以保证按钮文字清晰可读。

theme.css
sequence-timer-player {
  color-scheme: dark; /* light, dark, or inherit the page's */
  --sequence-primary: #60a5fa;
  --sequence-on-primary: #0b1220;
  --sequence-work: #60a5fa;
  --sequence-rest: #2dd4bf;
}

交付与自托管

浏览器模块位于 /embed/v1/,使用标准 JavaScript 模块,并允许跨站加载。只导入 player.js 不会加载编辑器。自托管时,将构建产物 apps/web/dist/embed/v1/ 整个目录复制到自己的 HTTPS 站点,保留共享模块文件并修改 import 地址。v1 地址提供兼容更新,请勿混用不同版本的文件。

仓库中的 @sequence-timer/* 目前是私有工作区包,尚未发布到公共 npm。外部网站请使用这里的浏览器模块。

嵌入组件不安装离线缓存、不读取本站偏好、不自动保存流程,也不发送统计事件。保存和分享由宿主页面决定;站点的安装与离线功能仅属于在线工具。