このページの中身

斜めの光条

CSS装飾 / 動く素材トップ

細長い光の帯を斜めに送り、静止面へ方向を足す。

試して調整する#

見た目を調整する

元の実装の既定値で動いています

判断#

向く場面: 速度、移動、技術を示すヒーローへ軽い方向感を加える場面。

避ける場面: 落ち着いた長文や、斜線をデータとして読む図の背面。

用意したプリセットは細い光条、広い光条。名前で選んだあと、実際に文字を重ねた状態で速さと密度を詰めます。

組み込み条件#

依存: 外部ライブラリと外部画像を使わず、単一HTML内のHTML、CSS、JavaScriptで動く。

負荷: Canvasを使わず、CSSで移動する要素を3つ以下に抑え、動きを減らす設定では静止させる。

再利用: ヒーローか一つの区切り面へ単独で使う。同じ画面で複数の動く背景を同時再生しない。

単一HTMLをコピーする#

下のコードだけで実物サンプルと同じ画面が動きます。調整欄を動かすと、値を反映したコードへ更新されます。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,follow">
<title>斜めの光条 実物サンプル</title>
<style>
:root {
  color-scheme: light dark;
  --ink-light:#1a2421; --mut-light:#5a6a72; --signal-light:#2a7ab8; --bg-light:#f4f8fb;
  --ink-dark:#f4f8fb; --mut-dark:#a8bac7; --signal-dark:#48b8ff; --bg-dark:#07111f;
}
* { box-sizing:border-box; }
html,body { margin:0; min-height:100%; }
body { font-family:"Hiragino Sans","Yu Gothic UI",system-ui,sans-serif; transition:background .3s,color .3s; }
html[data-demo-theme="light"], html[data-demo-theme="light"] body {
  background:var(--bg-light); color:var(--ink-light); --ink:var(--ink-light); --mut:var(--mut-light); --signal:var(--signal-light); }
html[data-demo-theme="dark"], html[data-demo-theme="dark"] body {
  background:var(--bg-dark); color:var(--ink-dark); --ink:var(--ink-dark); --mut:var(--mut-dark); --signal:var(--signal-dark); }
@media (prefers-color-scheme:light) {
  html:not([data-demo-theme]), html:not([data-demo-theme]) body {
    background:var(--bg-light); color:var(--ink-light); --ink:var(--ink-light); --mut:var(--mut-light); --signal:var(--signal-light); } }
@media (prefers-color-scheme:dark) {
  html:not([data-demo-theme]), html:not([data-demo-theme]) body {
    background:var(--bg-dark); color:var(--ink-dark); --ink:var(--ink-dark); --mut:var(--mut-dark); --signal:var(--signal-dark); } }
.sample { position:relative; min-height:100dvh; overflow:hidden; isolation:isolate; display:grid; align-items:end; }
.effect { position:absolute; inset:0; z-index:0; }
.effect canvas { display:block; width:100%; height:100%; touch-action:manipulation; }
.copy { position:relative; z-index:1; width:min(42rem,100%); padding:clamp(2rem,7vw,6rem); }
.label { margin:0 0 1rem; color:var(--signal); font:600 .72rem/1.4 ui-monospace,Consolas,monospace; letter-spacing:.16em; }
h1 { margin:0; max-width:14ch; font-size:clamp(2rem,5.5vw,4.5rem); line-height:1.05; letter-spacing:-.04em; }
.lead { margin:1.5rem 0 0; max-width:36rem; color:var(--mut); font-size:clamp(.95rem,1.4vw,1.1rem); line-height:1.85; }
.readout { position:absolute; inset:1.5rem 1.5rem auto auto; margin:0; color:var(--mut); font:500 .68rem/1.6 ui-monospace,Consolas,monospace; letter-spacing:.1em; writing-mode:vertical-rl; z-index:2; }
.theme-toggle { position:absolute; inset:1.5rem auto auto 1.5rem; z-index:2; display:flex; gap:.25rem; }
.theme-toggle button {
  min-block-size:36px; padding-inline:.75rem; font:600 .72rem/1 ui-monospace,Consolas,monospace;
  letter-spacing:.08em; border:1px solid var(--mut); background:transparent; color:var(--ink); cursor:pointer; border-radius:4px; }
.theme-toggle button[aria-pressed="true"] { background:var(--signal); color:var(--bg-dark); border-color:var(--signal); }
@media (max-width:640px) { .copy{padding:2rem 1.25rem 3rem}.readout{display:none} }
.bare .copy, .bare .readout, .bare .theme-toggle { display:none; }
</style>
<style>
:root { --streak:#5dc8ef; --streak-width:90px; --duration:14s; }
.streak-field { position:absolute; inset:-24%; overflow:hidden; transform:rotate(-18deg); }
.streak-field i { position:absolute; inset-block:0; inline-size:var(--streak-width);
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--streak) 32%,transparent),transparent);
  animation:streak-pass var(--duration) linear infinite; }
.streak-field i:nth-child(1){inset-inline-start:8%}.streak-field i:nth-child(2){inset-inline-start:42%;animation-delay:calc(var(--duration) * -.36)}
.streak-field i:nth-child(3){inset-inline-start:76%;animation-delay:calc(var(--duration) * -.7)}
@keyframes streak-pass { from{transform:translate3d(-45vw,0,0)} to{transform:translate3d(95vw,0,0)} }
.paused .streak-field i { animation-play-state:paused; }
@media (prefers-reduced-motion:reduce) { .streak-field i{animation:none;transform:translate3d(10vw,0,0)} }
</style>
</head>
<body>
<script id="effect-config" type="application/json">{"color":"#5dc8ef","speed":0.5,"width":90}</script>
<script>if (/[?&]bare=1/.test(location.search)) document.documentElement.classList.add('bare');
if (/[?&]tap=1/.test(location.search)) { addEventListener('load', function () {
  setTimeout(function () { [[0.5,0.5],[0.3,0.6],[0.7,0.4]].forEach(function (p, i) {
    setTimeout(function () { document.dispatchEvent(new MouseEvent('click',
      {clientX: innerWidth * p[0], clientY: innerHeight * p[1], bubbles: true})); }, i * 220);
  }); }, 300); }); }</script>
<script>(function(){var m=location.hash.match(/[#&]cfg=([^&]*)/);if(!m)return;
try{JSON.parse(decodeURIComponent(m[1]));document.getElementById('effect-config').textContent=decodeURIComponent(m[1]);}catch(e){}})();</script>
<script>(function(){
  var root=document.documentElement, cfgNode=document.getElementById('effect-config');
  var themeKeys=[];
  function syncTheme(t){
    root.setAttribute('data-demo-theme',t);
    document.querySelectorAll('.theme-toggle button').forEach(function(b){
      b.setAttribute('aria-pressed',b.dataset.theme===t?'true':'false');});
    if(!themeKeys.length)return;
    try{var cfg=JSON.parse(cfgNode.textContent||'{}');
      themeKeys.forEach(function(k){cfg[k]=t==='dark'?0:1;});
      cfgNode.textContent=JSON.stringify(cfg);}catch(e){}
  }
  var fixed=null;
  if(fixed){syncTheme(fixed);}else{var q=location.search.match(/[?&]theme=(light|dark)/); if(q) syncTheme(q[1]);}
  addEventListener('DOMContentLoaded',function(){
    document.querySelectorAll('.theme-toggle button').forEach(function(b){
      b.addEventListener('click',function(){syncTheme(b.dataset.theme);});});});})();</script>
<main class="sample">
  <div class="theme-toggle" aria-label="配色切替">
    <button type="button" data-theme="light" aria-pressed="false">LIGHT</button>
    <button type="button" data-theme="dark" aria-pressed="false">DARK</button>
  </div>
  <div class="effect" aria-hidden="true"><div class="streak-field"><i></i><i></i><i></i></div></div>
  <div class="copy">
    <p class="label">CSS DECORATION</p>
    <h1>斜めの光条</h1>
    <p class="lead">細長い光の帯を斜めに送り、静止面へ方向を足す。速度、移動、技術を示すヒーローへ軽い方向感を加える場面。</p>
  </div>
  <p class="readout">単一HTML・動きを減らす設定に対応</p>
</main>
<script>
const configNode = document.getElementById('effect-config');
const schema = {"color":{"type":"color"},"speed":{"type":"number","min":0.2,"max":1.4,"integer":false},"width":{"type":"number","min":40,"max":180,"integer":true}};
function clean(source) {
  const out = {};
  Object.keys(schema).forEach(key => {
    const rule = schema[key], value = source[key];
    if (rule.type === 'color') out[key] = /^#[0-9a-f]{6}$/i.test(String(value)) ? String(value) : '#ffffff';
    else out[key] = Math.min(rule.max, Math.max(rule.min, Number(value) || rule.min));
  });
  return out;
}
const root = document.documentElement;
const reduced = matchMedia('(prefers-reduced-motion: reduce)');
let active = true;
function report() {
  root.dataset.materialActive = String(active);
  root.dataset.materialRunning = String(active && !document.hidden && !reduced.matches);
}
function apply(source) {
  const cfg = clean(source);
  configNode.textContent = JSON.stringify(cfg);
  root.style.setProperty('--streak', cfg.color); root.style.setProperty('--streak-width', cfg.width + 'px'); root.style.setProperty('--duration', (7 / cfg.speed).toFixed(2) + 's');
}
apply(JSON.parse(configNode.textContent));
addEventListener('message', event => {
  const data = event.data || {};
  if (data.type === 'material-config') apply(data.values || {});
  if (data.type === 'material-active') { active = data.active === true; refresh(); }
});
function refresh() {
  root.classList.toggle('paused', !active || document.hidden || reduced.matches);
  report();
}
document.addEventListener('visibilitychange', refresh);
reduced.addEventListener('change', refresh);
addEventListener('pagehide', () => { active = false; refresh(); }, {once:true});
refresh();
</script>
</body>
</html>

AIエージェントへ渡す場合は、「このHTMLを対象ページのヒーロー背景へ組み込み、文字と主要ボタンを前面に残してください」と添えます。

組み込み時の確認#

関連#

この資料は、Webサイトを作るときに引くためのものです。 何のために作ったか。作っているのは AK²Lab。