横タイムライン
横方向にスクロールする年表タイムライン。年ごとの節目を線でつなぎ、現在地のノードだけ光の輪でパルスさせて強調表示します。
ライブデモ
コード
HTML
<!-- 横タイムライン: 横スクロールの年表。現在地のノードだけ強調表示 -->
<section class="tlh">
<div class="tlh__head">
<p class="tlh__eyebrow">BRAND HISTORY</p>
<h2 class="tlh__title">ブランドヒストリー</h2>
</div>
<div class="tlh__scroll" tabindex="0" role="region" aria-label="年表(横にスクロールできます)">
<ol class="tlh__track">
<li class="tlh__node">
<span class="tlh__year">2021</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">創業</span>
<span class="tlh__note">3人でサービス開発をスタート</span>
</li>
<li class="tlh__node">
<span class="tlh__year">2022</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">初期リリース</span>
<span class="tlh__note">最初のβ版を公開</span>
</li>
<li class="tlh__node">
<span class="tlh__year">2023</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">資金調達</span>
<span class="tlh__note">シリーズAを完了</span>
</li>
<li class="tlh__node">
<span class="tlh__year">2024</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">海外展開</span>
<span class="tlh__note">アジア圏でサービス開始</span>
</li>
<li class="tlh__node tlh__node--now">
<span class="tlh__year">2026</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">現在地</span>
<span class="tlh__note">月間利用者50万人を突破</span>
<span class="tlh__now-badge">NOW</span>
</li>
<li class="tlh__node tlh__node--future">
<span class="tlh__year">2027</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">次のフェーズ(予定)</span>
<span class="tlh__note">新プロダクトを開発中</span>
</li>
</ol>
</div>
</section>
CSS
/* 横タイムライン: 横スクロールの年表。現在地のノードだけ光の輪でパルスする */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 26px 0;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", system-ui, sans-serif;
background: radial-gradient(120% 140% at 50% -20%, #1c2350 0%, #0a0c18 60%);
color: #eceffb;
overflow-x: hidden;
}
/* ルート: 全幅フル */
.tlh { width: 100%; }
.tlh__head { text-align: center; padding: 0 20px; margin-bottom: 18px; }
.tlh__eyebrow {
margin: 0 0 6px;
font-size: 11px; font-weight: 700; letter-spacing: .24em;
color: #7fb2ff;
}
.tlh__title {
margin: 0;
font-size: clamp(19px, 3vw, 24px);
font-weight: 800;
letter-spacing: -.01em;
}
.tlh__scroll {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding: 6px 0 18px;
cursor: grab;
scrollbar-width: thin;
scrollbar-color: rgba(127, 178, 255, .4) transparent;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
}
.tlh__scroll:active { cursor: grabbing; }
.tlh__scroll::-webkit-scrollbar { height: 6px; }
.tlh__scroll::-webkit-scrollbar-thumb { background: rgba(127, 178, 255, .35); border-radius: 999px; }
.tlh__track {
position: relative;
display: flex;
list-style: none;
margin: 0;
padding: 30px 48px 4px;
width: max-content;
min-width: 100%;
}
/* 各ノードを貫く水平線 */
.tlh__track::before {
content: "";
position: absolute;
left: 30px; right: 30px; top: 63px;
height: 2px;
background: linear-gradient(90deg, rgba(127, 178, 255, .12), #7fb2ff 12%, #7fb2ff 82%, rgba(127, 178, 255, .12));
}
.tlh__node {
position: relative;
flex: 0 0 auto;
width: 188px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0 14px;
opacity: 0;
transform: translateY(14px);
animation: tlhIn .6s cubic-bezier(.22, 1, .36, 1) forwards;
}
.tlh__node:nth-child(1) { animation-delay: .05s; }
.tlh__node:nth-child(2) { animation-delay: .15s; }
.tlh__node:nth-child(3) { animation-delay: .25s; }
.tlh__node:nth-child(4) { animation-delay: .35s; }
.tlh__node:nth-child(5) { animation-delay: .45s; }
.tlh__node:nth-child(6) { animation-delay: .55s; }
.tlh__year {
font-size: 12px;
font-weight: 700;
letter-spacing: .06em;
color: #9aa6d6;
margin-bottom: 10px;
}
.tlh__pin {
position: relative;
z-index: 1;
width: 13px; height: 13px;
border-radius: 50%;
background: #7fb2ff;
box-shadow: 0 0 0 5px #0a0c18;
margin-bottom: 12px;
}
.tlh__label {
font-size: 14px;
font-weight: 800;
color: #f2f4ff;
margin-bottom: 4px;
}
.tlh__note {
font-size: 11.5px;
line-height: 1.55;
color: #a6acc9;
}
/* 現在地: 大きめのパルスするノード */
.tlh__node--now .tlh__pin {
width: 17px; height: 17px;
background: linear-gradient(135deg, #6de0ff, #7c8bff);
animation: tlhPulse 1.8s ease-out infinite;
}
.tlh__node--now .tlh__year,
.tlh__node--now .tlh__label { color: #8fe3ff; }
.tlh__now-badge {
display: inline-block;
margin-top: 8px;
font-size: 9.5px;
font-weight: 800;
letter-spacing: .12em;
color: #0a0c18;
background: linear-gradient(120deg, #6de0ff, #7c8bff);
padding: 3px 9px;
border-radius: 999px;
}
/* 未来(予定): 破線で控えめに */
.tlh__node--future .tlh__pin {
background: transparent;
border: 2px dashed #6a7296;
box-shadow: none;
}
.tlh__node--future .tlh__label,
.tlh__node--future .tlh__note { opacity: .65; }
@keyframes tlhIn {
to { opacity: 1; transform: translateY(0); }
}
@keyframes tlhPulse {
0% { box-shadow: 0 0 0 5px #0a0c18, 0 0 0 5px rgba(109, 224, 255, .45); }
70% { box-shadow: 0 0 0 5px #0a0c18, 0 0 0 15px rgba(109, 224, 255, 0); }
100% { box-shadow: 0 0 0 5px #0a0c18, 0 0 0 15px rgba(109, 224, 255, 0); }
}
@media (max-width: 480px) {
.tlh__node { width: 156px; padding: 0 10px; }
.tlh__track { padding: 26px 24px 4px; }
.tlh__track::before { left: 16px; right: 16px; top: 59px; }
}
@media (prefers-reduced-motion: reduce) {
.tlh__node { animation: none !important; opacity: 1; transform: none; }
.tlh__node--now .tlh__pin { animation: none; }
}
JavaScript
// 横タイムライン: 初期表示で「現在地」ノードが見える位置へスクロールし、
// ドラッグでも横スクロールできるようにする(無くても表示・操作は成立する補助機能)
(() => {
const scroller = document.querySelector('.tlh__scroll');
const now = document.querySelector('.tlh__node--now');
if (!scroller) return; // null安全
// 現在地ノードをスクロール領域の中央付近に合わせる
if (now) {
const target = Math.max(0, now.offsetLeft - scroller.clientWidth / 2 + now.offsetWidth / 2);
scroller.scrollLeft = target;
}
// ポインタドラッグで横スクロール
let dragging = false, startX = 0, startScroll = 0;
scroller.addEventListener('pointerdown', (e) => {
dragging = true; startX = e.clientX; startScroll = scroller.scrollLeft;
scroller.setPointerCapture(e.pointerId);
});
scroller.addEventListener('pointermove', (e) => {
if (!dragging) return;
scroller.scrollLeft = startScroll - (e.clientX - startX);
});
const endDrag = () => { dragging = false; };
scroller.addEventListener('pointerup', endDrag);
scroller.addEventListener('pointercancel', endDrag);
scroller.addEventListener('pointerleave', endDrag);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「横タイムライン」の効果を追加してください。
# 追加してほしい効果
横タイムライン(タイムライン & 404)
横方向にスクロールする年表タイムライン。年ごとの節目を線でつなぎ、現在地のノードだけ光の輪でパルスさせて強調表示します。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 横タイムライン: 横スクロールの年表。現在地のノードだけ強調表示 -->
<section class="tlh">
<div class="tlh__head">
<p class="tlh__eyebrow">BRAND HISTORY</p>
<h2 class="tlh__title">ブランドヒストリー</h2>
</div>
<div class="tlh__scroll" tabindex="0" role="region" aria-label="年表(横にスクロールできます)">
<ol class="tlh__track">
<li class="tlh__node">
<span class="tlh__year">2021</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">創業</span>
<span class="tlh__note">3人でサービス開発をスタート</span>
</li>
<li class="tlh__node">
<span class="tlh__year">2022</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">初期リリース</span>
<span class="tlh__note">最初のβ版を公開</span>
</li>
<li class="tlh__node">
<span class="tlh__year">2023</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">資金調達</span>
<span class="tlh__note">シリーズAを完了</span>
</li>
<li class="tlh__node">
<span class="tlh__year">2024</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">海外展開</span>
<span class="tlh__note">アジア圏でサービス開始</span>
</li>
<li class="tlh__node tlh__node--now">
<span class="tlh__year">2026</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">現在地</span>
<span class="tlh__note">月間利用者50万人を突破</span>
<span class="tlh__now-badge">NOW</span>
</li>
<li class="tlh__node tlh__node--future">
<span class="tlh__year">2027</span>
<span class="tlh__pin" aria-hidden="true"></span>
<span class="tlh__label">次のフェーズ(予定)</span>
<span class="tlh__note">新プロダクトを開発中</span>
</li>
</ol>
</div>
</section>
【CSS】
/* 横タイムライン: 横スクロールの年表。現在地のノードだけ光の輪でパルスする */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 26px 0;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", system-ui, sans-serif;
background: radial-gradient(120% 140% at 50% -20%, #1c2350 0%, #0a0c18 60%);
color: #eceffb;
overflow-x: hidden;
}
/* ルート: 全幅フル */
.tlh { width: 100%; }
.tlh__head { text-align: center; padding: 0 20px; margin-bottom: 18px; }
.tlh__eyebrow {
margin: 0 0 6px;
font-size: 11px; font-weight: 700; letter-spacing: .24em;
color: #7fb2ff;
}
.tlh__title {
margin: 0;
font-size: clamp(19px, 3vw, 24px);
font-weight: 800;
letter-spacing: -.01em;
}
.tlh__scroll {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding: 6px 0 18px;
cursor: grab;
scrollbar-width: thin;
scrollbar-color: rgba(127, 178, 255, .4) transparent;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
}
.tlh__scroll:active { cursor: grabbing; }
.tlh__scroll::-webkit-scrollbar { height: 6px; }
.tlh__scroll::-webkit-scrollbar-thumb { background: rgba(127, 178, 255, .35); border-radius: 999px; }
.tlh__track {
position: relative;
display: flex;
list-style: none;
margin: 0;
padding: 30px 48px 4px;
width: max-content;
min-width: 100%;
}
/* 各ノードを貫く水平線 */
.tlh__track::before {
content: "";
position: absolute;
left: 30px; right: 30px; top: 63px;
height: 2px;
background: linear-gradient(90deg, rgba(127, 178, 255, .12), #7fb2ff 12%, #7fb2ff 82%, rgba(127, 178, 255, .12));
}
.tlh__node {
position: relative;
flex: 0 0 auto;
width: 188px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0 14px;
opacity: 0;
transform: translateY(14px);
animation: tlhIn .6s cubic-bezier(.22, 1, .36, 1) forwards;
}
.tlh__node:nth-child(1) { animation-delay: .05s; }
.tlh__node:nth-child(2) { animation-delay: .15s; }
.tlh__node:nth-child(3) { animation-delay: .25s; }
.tlh__node:nth-child(4) { animation-delay: .35s; }
.tlh__node:nth-child(5) { animation-delay: .45s; }
.tlh__node:nth-child(6) { animation-delay: .55s; }
.tlh__year {
font-size: 12px;
font-weight: 700;
letter-spacing: .06em;
color: #9aa6d6;
margin-bottom: 10px;
}
.tlh__pin {
position: relative;
z-index: 1;
width: 13px; height: 13px;
border-radius: 50%;
background: #7fb2ff;
box-shadow: 0 0 0 5px #0a0c18;
margin-bottom: 12px;
}
.tlh__label {
font-size: 14px;
font-weight: 800;
color: #f2f4ff;
margin-bottom: 4px;
}
.tlh__note {
font-size: 11.5px;
line-height: 1.55;
color: #a6acc9;
}
/* 現在地: 大きめのパルスするノード */
.tlh__node--now .tlh__pin {
width: 17px; height: 17px;
background: linear-gradient(135deg, #6de0ff, #7c8bff);
animation: tlhPulse 1.8s ease-out infinite;
}
.tlh__node--now .tlh__year,
.tlh__node--now .tlh__label { color: #8fe3ff; }
.tlh__now-badge {
display: inline-block;
margin-top: 8px;
font-size: 9.5px;
font-weight: 800;
letter-spacing: .12em;
color: #0a0c18;
background: linear-gradient(120deg, #6de0ff, #7c8bff);
padding: 3px 9px;
border-radius: 999px;
}
/* 未来(予定): 破線で控えめに */
.tlh__node--future .tlh__pin {
background: transparent;
border: 2px dashed #6a7296;
box-shadow: none;
}
.tlh__node--future .tlh__label,
.tlh__node--future .tlh__note { opacity: .65; }
@keyframes tlhIn {
to { opacity: 1; transform: translateY(0); }
}
@keyframes tlhPulse {
0% { box-shadow: 0 0 0 5px #0a0c18, 0 0 0 5px rgba(109, 224, 255, .45); }
70% { box-shadow: 0 0 0 5px #0a0c18, 0 0 0 15px rgba(109, 224, 255, 0); }
100% { box-shadow: 0 0 0 5px #0a0c18, 0 0 0 15px rgba(109, 224, 255, 0); }
}
@media (max-width: 480px) {
.tlh__node { width: 156px; padding: 0 10px; }
.tlh__track { padding: 26px 24px 4px; }
.tlh__track::before { left: 16px; right: 16px; top: 59px; }
}
@media (prefers-reduced-motion: reduce) {
.tlh__node { animation: none !important; opacity: 1; transform: none; }
.tlh__node--now .tlh__pin { animation: none; }
}
【JavaScript】
// 横タイムライン: 初期表示で「現在地」ノードが見える位置へスクロールし、
// ドラッグでも横スクロールできるようにする(無くても表示・操作は成立する補助機能)
(() => {
const scroller = document.querySelector('.tlh__scroll');
const now = document.querySelector('.tlh__node--now');
if (!scroller) return; // null安全
// 現在地ノードをスクロール領域の中央付近に合わせる
if (now) {
const target = Math.max(0, now.offsetLeft - scroller.clientWidth / 2 + now.offsetWidth / 2);
scroller.scrollLeft = target;
}
// ポインタドラッグで横スクロール
let dragging = false, startX = 0, startScroll = 0;
scroller.addEventListener('pointerdown', (e) => {
dragging = true; startX = e.clientX; startScroll = scroller.scrollLeft;
scroller.setPointerCapture(e.pointerId);
});
scroller.addEventListener('pointermove', (e) => {
if (!dragging) return;
scroller.scrollLeft = startScroll - (e.clientX - startX);
});
const endDrag = () => { dragging = false; };
scroller.addEventListener('pointerup', endDrag);
scroller.addEventListener('pointercancel', endDrag);
scroller.addEventListener('pointerleave', endDrag);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。