ドキュメント / Web Components

タイマーを埋め込む

プレーヤーとエディターは標準の Web Components です。フレームワークなしで、どのページにもプレーヤーを埋め込めます。

  • ビルド不要
  • データを送信しない
  • CSS 変数でテーマ設定

タイマーを再生する

完全な HTML の例をそのままページにコピーしてください。配信しているブラウザ用モジュールは依存関係を含んでいるので、パッケージのインストールやビルドツールは不要です。sequence プロパティを設定し、ブラウザが音を出せるよう、スタートは訪問者に押してもらいます。

プレーヤーの下部には、既定で「Powered by EigenTime Timer」という小さなリンクが表示され、新しいタブでこのサイトを開きます。ただのリンクなので、クリックされるまで何も読み込みません。no-attribution 属性を付けると非表示になります。

player-example.html例を開く
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="robots" content="noindex">
  <title>My timer</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>My timer</h1>
  <sequence-timer-player locale="en-US"></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: { 'en-US': 'My timer' } },
      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 = 'Sequence complete.';
    });
    // Exit stops the run and leaves fullscreen; the page decides what follows,
    // such as closing a dialog. Pages with nothing to do can hide ::part(exit).
    player.addEventListener('exit', () => {
      document.querySelector('[data-status]').textContent = 'Stopped. What Exit does next is up to your page.';
    });
    // Playback starts only when the visitor presses Start.
  </script>
</body>
</html>

エディターとプレーヤー

エディターとプレーヤーは別々に読み込まれます。エディターの start イベントは検証済みのシーケンスを渡し、例ではそれを同じプレーヤーコンポーネントに渡しています。

builder-example.html例を開く
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="robots" content="noindex">
  <title>My timer</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>My timer</h1>
  <sequence-timer-builder locale="en-US"></sequence-timer-builder>
  <sequence-timer-player locale="en-US"></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: { 'en-US': 'My timer' } },
      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 = 'Sequence complete.';
    });
    // Exit stops the run and leaves fullscreen; the page decides what follows,
    // such as closing a dialog. Pages with nothing to do can hide ::part(exit).
    player.addEventListener('exit', () => {
      document.querySelector('[data-status]').textContent = 'Stopped. What Exit does next is up to your page.';
    });
    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 の各イベントを受け取って、独自の UI や分析に使えます。コンポーネント自身はどこにもデータを送りません。

「終了」ボタンは計時を止めて全画面を解除し、exit イベントを送ります。その後どうするか(ダイアログを閉じる、エディターに戻るなど)はページ側で決めます。終了の必要がないページでは ::part(exit) { display: none; } で隠せます。

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');
});

player.addEventListener('exit', () => {
  // Stopped and out of fullscreen: close your dialog, go back, …
});

テーマ設定

色とサイズは 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;
}

サイズとレイアウト

プレーヤーは自身の幅に合わせてレイアウトします。要素に幅を与えれば、リングと文字もそれに合わせて大きさが変わります。幅 800px 以上ではリングを左、表示とボタンを右に置きます。layout="stacked" は常に縦に積み、layout="split" はボタンが収まる 576px 以上で左右に並べます。

--sequence-dial-size はページ上のリングの最大サイズ(既定 440px)で、clamp() を使えばビューポートの高さに合わせられます。全画面ではリングは常に画面に収まります。

さらに細かく調整するには、::part() で dial、phase、time、note、stats、next、controls、toolbar、exit、attribution の各パーツにスタイルを当てます。no-controls を付けるとボタンがすべて隠れ、ページ側で再生を操作できます。

レイアウト
配色
隠す
embed.html
<sequence-timer-player locale="ja-JP"></sequence-timer-player>
<style>
  sequence-timer-player {
    max-width: 720px;
  }
</style>

配信とセルフホスト

ブラウザ用モジュールは /embed/v1/ の下にあり、別オリジンからの import を許可しています。player.js だけを import すればエディターは読み込まれません。これらの URL を直接使うのがいちばん簡単です。v1 のファイルはその場で更新され、変更は後方互換のものに限られます。npm パッケージはまだありません。

自分でコピーをホストするには、下のコマンドでモジュールをサイトの 1 つのフォルダーにダウンロードします。player.js、エディターを使うなら builder.js、両者が共有するモジュール(名前にバージョンのハッシュが付きます)、サードパーティのライセンスです。そのうえで、例の import をコピー先(たとえば /sequence-timer/player.js)に向けます。

コピーは自動では更新されません。更新するときは、1 つのファイルだけを置き換えず、フォルダー全体をダウンロードし直してください。ファイルはハッシュ付きの名前で互いを import しているため、別のリリースのファイルを混ぜると読み込めません。

download.sh
# Download into sequence-timer/, then put the whole folder on your site
mkdir sequence-timer && cd sequence-timer
base=https://timer.eigentime.org/embed/v1
curl -fO "$base/player.js" -fO "$base/builder.js" -fO "$base/THIRD-PARTY-LICENSES.txt"
# The shared module's name changes with each release; read it from the imports
for file in $(grep -ho '"\./[^"]*\.js"' player.js builder.js | tr -d '"' | sort -u); do
  curl -fO "$base/${file#./}"
done

埋め込んだコンポーネントは、オフライン用のワーカーを登録せず、サイトの設定を読まず、シーケンスを自動保存せず、分析データも送りません。保存と共有はホスト側が管理します。インストールとオフライン対応はオンライン版のツールだけの機能です。