GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

ホログラム シール デザイン 9種 — 白フチから剥がれる角まで

このホログラム シール デザイン 9種のうち、1つは白フチと影だけでダイカットステッカーに見せ、1つはポインタの角度に沿って表面を虹色の光が走り抜け、1つはカーソルに合わせてわずかに傾いて本物のシールに触れる感触を出します。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

以下の9種はどれもフレームワーク不要・ビルド不要の素のHTML/CSS/JSファイル1本で完結しており、フォルダを1つ取り出して index.html をブラウザで開くだけでそのまま動きます。ホログラム シール 作り方を調べている方が実際に手を動かして確かめられるよう、各パターンの計算層だけを次の章で切り出します。

デモ

上の各カードにマウスを乗せる・クリックする・ドラッグすると、それぞれ異なる剥がれ方・傾き方・スナップの仕方を確認できます。以下の説明はすべて記憶からの要約ではなく、各デモの実際のソースを読んで書いています。

9パターンの内訳 — 計算が1層だけ走る場所

下の表はどのパターンも同じ枚数だけキャプチャしたフレームの実測値です。累積面積は動いた部分が各フレームでどれだけの割合を占めたかを合計した値、平均強度は動いたフレームごとのピクセル変化の強さ、動いたフレーム数(23フレーム中)は23回のフレーム間比較のうち実際に差分があった回数を指します。

# パターン 累積面積 平均強度 動いたフレーム(23フレーム中)
01 白フチのダイカットステッカーカード 7.18% 100.8 15
02 角からめくれるステッカー 0.11% 71.9 22
03 角度に追従するホログラムステッカー 14.42% 24.9 23
04 マスキングテープで留めたステッカー 10.05% 78.3 20
05 フォルダタブ型ステッカー 1.11% 127.5 15
06 カーソルに合わせて傾くステッカー 5.16% 57.7 23
07 ランダムな角度で散らばったステッカーの山 18.39% 101.0 17
08 ドラッグして貼るステッカー 21.61% 128.2 16
09 切り取り線が見えるステッカーシート 8.43% 72.3 23

累積面積が最大なのは08で21.61%、格子1マス分をドラッグして動かす方が、02の角がほんの0.11%だけめくれるより遥かに多くのピクセルを動かすためです。平均強度も08が128.2で最も高く、05の127.5とはわずか0.7差にとどまります — チップがスナップする瞬間とcanvasで実測したタブの縁は、面積が違っても変化そのものは鋭いということです。03・06・09はいずれも23フレーム中23回動いており並んで最多ですが、03自身の平均強度は24.9で9種中最も低く、色と光がゆっくり流れる変化はスナップや点線の行進ほどピクセルを激しく変えないからです。

クラスターA — ポインタのベクトルが毎フレーム角度に変わる(02・03・06)

02・03・06はどれもポインタの現在座標を Math.atan2() か -1..1 の正規化にかけ、毎フレーム角度や回転量を作り出します。めくれのclip-path角度も、光沢の色相とスイープの方向も、カードのrotateX/rotateYも、あらかじめ決めた位置の集合ではなく今ポインタがある場所そのものから生まれます。

クラスターB — ひとつの数値が固定式を動かす(01・04・09)

01・04・09は描画のたびにポインタ座標を読む必要がありません。持ち上がり比率・角の番号・経過時間という1つの数値だけで、box-shadowの重なりもテープの傾きも点線の行進オフセットも1本の算術式で決まります。

クラスターC — 入力が決まった状態の1つに収束する(05・07・08)

05・07・08は入力を少数の決まった結果のどれかに落とし込みます。canvasで実測したラベル幅を最小値と最大値の間に収める、短い固定リストから引いたシード値のハッシュを使う、ポインタ位置を2×2の格子に丸める、というように連続値のまま扱うことはありません。

パターンごとのコード

以下の9本はそれぞれのソース全体から、実際に動きを決めている計算層だけを切り出した短い断片です。フレームワークは使っていません。

01白フチのダイカットステッカーカード

画像やアイコンの周りに太い白フチを回し、背後に柔らかい影を敷いて、実際に印刷して切り抜いたステッカーをその場に貼ったように見せます。

白フチのダイカットステッカーカード
新しいタブで開く

01 · 白フチのダイカットステッカーカード — 計測 累積面積 7.179% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

box-shadowclip-pathpointerenter
  function shadowFor(t) {
    var parts = [];
    for (var i = 1; i <= LIFT_LAYERS; i++) {
      var off = (LAYER_OFFSET * i * t).toFixed(2);
      var blur = (LAYER_BLUR * i * t + 2).toFixed(2);
      var alpha = Math.max(0, LAYER_ALPHA_BASE - LAYER_ALPHA_STEP * i) * t;
      parts.push('0 ' + off + 'px ' + blur + 'px rgba(10,8,14,' + alpha.toFixed(3) + ')');
    }
    return parts.join(', ');
  }

02角からめくれるステッカー

カードにカーソルを乗せると片方の角が紙のように後ろへめくれ上がり、その下に隠れていた別の色が少し見えます — 133のホログラムバッジのめくれ+スイープの組み合わせとは異なる実演で、ここではめくれる角度自体がポインタの位置に追従します。

角からめくれるステッカー
新しいタブで開く

02 · 角からめくれるステッカー — 計測 累積面積 0.108% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-pathatan2pointermove
  function applyFromPoint(px, py) {
    var r = card.getBoundingClientRect();
    var pivotX = r.right, pivotY = r.top;
    var dx = pivotX - px, dy = py - pivotY;
    var distance = Math.hypot(dx, dy);
    var amt = Math.max(0, Math.min(1, 1 - distance / PEEL_REACH));
    targetSize = amt * PEEL_CORNER_SIZE;
    targetDeg = (Math.atan2(dy, dx) * 180 / Math.PI + 360) % 360;
  }

03角度に追従するホログラムステッカー

表面の上に虹色の薄い膜を重ね、カーソルが動く方向と速さに合わせてその光を表面を横切って走らせます — 133の静的なカード・バッジのホログラムセットとは異なる実演で、ここではポインタの移動量そのものが光の移動速度を直接決めます。外部画像を使わず、conic-gradientをmix-blend-modeで重ねるだけのホログラム シート シール 作り方の一例として見ることもできます。

角度に追従するホログラムステッカー
新しいタブで開く

03 · 角度に追従するホログラムステッカー — 計測 累積面積 14.422% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

conic-gradientmix-blend-modeatan2
  function applyMove(x, y, now) {
    if (lastX !== null) {
      var dt = Math.max(now - lastTime, 1);
      var dx = x - lastX, dy = y - lastY;
      var speed = Math.hypot(dx, dy) / dt; // px/ms
      hue = (hue + speed * HUE_SPEED * (dt / 1000)) % 360;
      sweepPos = (sweepPos + speed * SWEEP_SPEED * (dt / 1000)) % 200;
      sweepAngleTarget = 90 + (Math.atan2(dy, dx) * 180 / Math.PI) % HOLO_SWEEP_ANGLE_RANGE;
    }
    lastX = x; lastY = y; lastTime = now;
  }

04マスキングテープで留めたステッカー

カードの角二か所に半透明のテープ片を斜めに乗せて、実際に手でテープを貼って留めたスクラップブックのように見せます。

マスキングテープで留めたステッカー
新しいタブで開く

04 · マスキングテープで留めたステッカー — 計測 累積面積 10.047% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

box-shadowtransform-originrequestAnimationFrame
  // 2本のテープの傾きは、角の番号(i)を base + i*step という1本の式に通して求める —
  // 符号だけ左右で交互に反転させ、固定の角度を2つ持たせず1つの式から両方を作る。
  function tapeAngle(i) {
    var sign = i % 2 === 0 ? -1 : 1;
    return sign * (TAPE_BASE_ANGLE + TAPE_ANGLE_STEP * i);
  }
  var baseAngleA = tapeAngle(1);
  var baseAngleB = tapeAngle(2);

05フォルダタブ型ステッカー

上の角が書類フォルダのタブのように飛び出た形に切り抜かれ、そのタブ部分に名前を書いてカテゴリを分けます。

フォルダタブ型ステッカー
新しいタブで開く

05 · フォルダタブ型ステッカー — 計測 累積面積 1.111% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-pathmeasureText--tab-w
  // ラベルの実際の文字幅をcanvasで測り、タブが常にその文字にぴったり合う幅を持つようにする —
  // 幅はあらかじめ決めた値ではなく、毎回実測した値になる。箱そのものは最大幅で固定しておき、
  // clip-pathで見せる幅だけを変えるので(transform・opacityだけを動かす原則と同じ狙いで)、レイアウトの再計算が起きない。
  function measureWidth(text) {
    ctx.font = '600 13px -apple-system, "Segoe UI", "Noto Sans KR", sans-serif';
    var w = ctx.measureText(text).width;
    return Math.max(TAB_MIN, Math.min(TAB_MAX, w + TAB_PADDING));
  }

06カーソルに合わせて傾くステッカー

カーソルがカード上のどこにあるかに応じてカードがその反対方向へわずかに立体的に傾き、表面のハイライトも一緒に動いて厚みがあるように見せます。

カーソルに合わせて傾くステッカー
新しいタブで開く

06 · カーソルに合わせて傾くステッカー — 計測 累積面積 5.158% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

rotateXrotateYperspective
  // ポインタ座標をカード中心基準で-1..1に正規化し、その値にMAX_TILTを掛けて回転角を得る。
  // 縦方向の移動はrotateXを、横方向の移動はrotateYを逆符号で決める(上に押すと奥へ傾く)。
  function applyFromPoint(px, py) {
    var rect = zone.getBoundingClientRect();
    var nx = ((px - rect.left) / rect.width) * 2 - 1;
    var ny = ((py - rect.top) / rect.height) * 2 - 1;
    nx = Math.max(-1, Math.min(1, nx));
    ny = Math.max(-1, Math.min(1, ny));
    targetRY = nx * MAX_TILT;
    targetRX = -ny * MAX_TILT;
    targetGX = 50 + nx * GLARE_RANGE;
    targetGY = 50 + ny * GLARE_RANGE;
  }

07ランダムな角度で散らばったステッカーの山

複数のステッカーをそれぞれ違う角度で少し重ねて散らし置きますが、角度はあらかじめ決めた順番だけから選ぶので、何度読み込んでも同じ配置が再現されます。

ランダムな角度で散らばったステッカーの山
新しいタブで開く

07 · ランダムな角度で散らばったステッカーの山 — 計測 累積面積 18.395% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

Math.sinbox-shadowrequestAnimationFrame
  // 決定的な散らし(シード固定、Math.random不使用) — サイン波のハッシュで、1つのシード値から
  // 常に同じ小数部を取り出す。シードが同じなら何度呼んでも同じ結果になる関数。
  function hash(seed) {
    var v = Math.sin(seed * 12.9898 + 78.233) * 43758.5453;
    return v - Math.floor(v);
  }

  function layoutFor(seed) {
    var h1 = hash(seed);
    var h2 = hash(seed + 100);
    var h3 = hash(seed + 200);
    return {
      rot: (h1 * 2 - 1) * SCATTER_ROTATE_RANGE,
      dx: (h2 * 2 - 1) * SCATTER_POS_RANGE,
      dy: (h3 * 2 - 1) * SCATTER_POS_RANGE
    };
  }

08ドラッグして貼るステッカー

ステッカーを指やマウスで押さえてボード上の好きな場所までドラッグすると、離した瞬間その場に少し回転した状態で貼り付き、他のステッカーの上に重なります。

ドラッグして貼るステッカー
新しいタブで開く

08 · ドラッグして貼るステッカー — 計測 累積面積 21.61% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

setPointerCapturetouch-actionMath.round
  // ポインタが置かれた座標をGRIDのマスサイズで割った商を四捨五入し、マス中心の座標に戻す —
  // 自由な座標ではなく、常に格子上のどれか1点にスナップする計算になっている。
  function snap(px, py) {
    var col = Math.max(0, Math.min(1, Math.round((px - GRID / 2) / GRID)));
    var row = Math.max(0, Math.min(1, Math.round((py - GRID / 2) / GRID)));
    return { x: col * GRID + GRID / 2, y: row * GRID + GRID / 2 };
  }

09切り取り線が見えるステッカーシート

複数のステッカーを格子状に並べたシートの上に点線の切り取り線を描き、1枚を押すとその点線に沿って型抜きされたように少し手前へ飛び出します。

切り取り線が見えるステッカーシート
新しいタブで開く

09 · 切り取り線が見えるステッカーシート — 計測 累積面積 8.425% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

strokeDasharraystrokeDashoffsetaria-pressed
  // 点線の1区切り分(PATTERN)に対して経過時間を掛けたオフセットをモジュロ演算で巻き戻し、
  // 途切れず回り続ける裁断線(stroke-dashoffset)を作る — 実際の印刷裁断線の行進表示と同じ計算。
  function loop(ts) {
    if (!last) last = ts;
    last = ts;
    if (isDemo && !reduce) autoAdvance(ts);
    if (!reduce) {
      var offset = (ts * MARCH_SPEED / 1000) % PATTERN;
      rects.forEach(function (r) { r.style.strokeDashoffset = (-offset).toFixed(2); });
    }
    requestAnimationFrame(loop);
  }

値が変わるとUIがどう変わるか — 角度・サイズ・タイミング・順番

どのパターンも、実際に働いている変数はたった1つです。その変数を変えるだけでパターン全体の印象が変わります。

01 — 持ち上がり比率

バッジのbox-shadowの重なりは shadowFor(t) から来ており、t は0(貼り付いた状態)から1(完全に持ち上がった状態)まで動くので、5層すべてのオフセット・ぼかし・不透明度がこの1つの比率と連動して増減します。

02 — 角へのベクトル

applyFromPoint() はポインタとカード右上角との距離と Math.atan2() の角度を、めくれのサイズとグラデーション角度に変換するので、ポインタが実際にその角へ近づいたときだけめくれます。

03 — ポインタの速度

applyMove() はポインタのフレーム間移動量を経過時間で割ってミリ秒あたりのピクセル速度を求め、その1つの速度が色相の回転とスイープ位置の両方を同時に動かします。

04 — 番号の算術

tapeAngle(i) は sign * (TAPE_BASE_ANGLE + TAPE_ANGLE_STEP * i) を返すので、2本のテープの角度は別々に選んだ2つの数値ではなく、角の番号を渡した1本の式から生まれます。

05 — 実測した幅

measureWidth() は隠しcanvasから実際のピクセル幅を読み取り、TAB_MIN と TAB_MAX の間に収めるので、フォルダタブの幅は常に実際の文字に合い、当て推量の値になりません。

06 — 正規化した位置

applyFromPoint() はカード内のポインタ位置を各軸で-1..1に正規化し MAX_TILT を掛けるので、カードの回転もハイライトの位置も、今ポインタがどこにあるかをそのまま読み取った値です。

07 — シード付きハッシュ

hash(seed) は固定のシードを Math.sin() に通して小数部だけを残すので、layoutFor() は同じシードに対して常に同じ回転量とずれを返します — その場で新しく作り出しているわけではありません。

08 — 格子へのスナップ

snap() はポインタの生座標を GRID で割って四捨五入し、また掛け戻すことで、自由な位置を最も近い格子マスの中心に変えます。

09 — 経過時間のモジュロ

裁断線のオフセットは (ts * MARCH_SPEED / 1000) % PATTERN から来ており、行進の速さはコマ送りのキーフレームではなく経過時間の直接の関数になっています。

どこで壊れるか — アクセシビリティ・reduced-motion・キーボード

03のカードはネイティブな <button> なので、キーボードユーザーもTabで到達できます。しかし色相の回転と光のスイープが反応するのは pointermove だけで、focus/blur も監視している01・02・06とは異なります。ホログラムのカードにTabで移動してEnterを押しても、光沢には何も起きません — applyMove() は座標の実際の差分がないと速度を計算できないので、キーボードだけの訪問者はこのパターンが見せたい効果を一度も見ないまま終わります。これは描画のクセではなく実在するギャップで、実際の製品に載せる前に focus ハンドラで塞ぐ価値があります。同じ「見た目は同じボタン」でも、01・02・06のように focus/blur まで面倒を見ているか、03のように pointermove 一本で済ませているかで、キーボード利用者に届く体験はまったく別物になるという点は、9種を並べて実測してみないと気づきにくい差です。

reduced-motionで消えるもの

  • 01 ダイカットの持ち上がり — 消えるのはイージングと自動デモループだけで、box-shadowの重なり自体は pointerenter/focus で持ち上がった状態・戻った状態へ即座に切り替わります。
  • 02 めくれる角 — サイズと角度が滑らかに動く代わりに目標値へ即座に切り替わり、デモ自身の自動周回ループも止まります。
  • 03 ホログラムの光沢 — 色相とスイープ位置がそのまま0に固定され、虹色の色変化も光の帯もまったく再生されず、静止した斜めの光沢だけが残ります。
  • 04 テープ — 傾きと持ち上がりが留めた状態・剥がれた状態へ即座に切り替わり、自動デモの交互めくりループも止まります。
  • 05 フォルダタブ — タブの幅が新しいラベルの幅へ滑らかさなしに即座に切り替わり、3つのラベルを自動で巡回する動きも止まります。
  • 06 カーソル傾き — rotateX/rotateYとハイライト位置が目標値へ即座に切り替わり、自動で周回するデモループも止まります。
  • 07 散らばりの山 — 各チップの回転とずれが新しいハッシュ配置へ即座に切り替わり、時間差での自動再シャッフルも止まります。
  • 08 ドラッグして配置 — 離した瞬間や矢印キーで格子マスへスナップする動作自体は残りますが途中の滑らかさが消え、自動で巡回するデモループも完全に止まります。
  • 09 切り取り線 — 行進するオフセットは完全に静止したまま動かなくなりますが、ステッカーをクリックすると即座に前へ飛び出す動作はそのまま残ります。

上の9本のソースはすべて共通の1つのパスワードdkbn5f83の内側にあり、アーカイブを一度解凍するだけでこの記事のすべてのパターンに対応するHTML・CSS・JSファイルが手元に揃い、そのままブラウザで開けます。

3つのバリエーションとアクセシビリティ

バリエーション 値 感触
ホログラムスイープの角度範囲を広げる 03 HOLO_SWEEP_ANGLE_RANGE 45deg → 90deg 光の帯が流れる基準角が広がり、ポインタを少し動かすだけでスイープが対角線を大きく横切って方向が急に曲がる — 45degに比べて反応が誇張されて見える。
めくれる角のサイズを大きくする 02 PEEL_CORNER_SIZE 24px → 44px 最大にめくれた時に現れる三角形がほぼ倍のサイズになり、隠れていた裏面の色が角の飾りではなくカード内容の一部のように目立つ — さりげないヒントが明白な露出に変わる。
ランダムスキャッターの角度幅を狭める 07 SCATTER_ROTATE_RANGE 30deg → 10deg 同じハッシュ値でも回転幅が狭まり、ステッカーがほぼ整列して見える — 無造作に積まれた山から、わずかにずれただけの陳列棚のような印象に変わる。

01・02・03・06・08は説明的な aria-label を持つネイティブな <button type="button"> なので、EnterとSpaceは追加のkeydownコードなしですでにトリガーやフォーカスとして働きます。04のピン留めトグルと09の2つのサイコロボタンはどちらも aria-pressed で状態を伝え、08はさらに矢印キーの keydown も監視しているので、ポインタなしでもチップを格子マスの間で移動できます。

FAQ

この9種のホログラム シート シール 作り方はフレームワークやビルド手順が必要ですか?

いいえ。どのパターンもビルド手順もフレームワークも使わない、単独で完結したHTML/CSS/JSファイル1本です。index.htmlをそのまま開くか、マークアップをどのページに置いても同じように動きます。

prefers-reduced-motionが本当に効いているかはどう確かめられますか?

OSのアクセシビリティ設定で「視差効果を減らす」を有効にしてから、上の9種のデモをどれか読み込み直してください。それぞれが起動時に window.matchMedia('(prefers-reduced-motion: reduce)') を読み取り、自分のアニメーションを取り除くか止めるので、そのパターンのCSSやJSと突き合わせて直接確認できます。

3d ホログラム シール 作り方として、クリックやドラッグなしでも反応するのはどれですか?

01・02・03・06はポインタが乗った瞬間や動いた瞬間にすぐ反応するので、追加の操作なしで反応します。04・05・07・08・09はクリック・ドラッグ・キー操作という1回限りの動作を待ちます — テープ、フォルダタブ、散らばりの山、ドラッグ先、切り取り線はいずれも継続的なホバー状態ではなく一度きりの操作だからです。

出典: MDN — clip-path、MDN — mix-blend-mode、MDN — Pointer events、MDN — conic-gradient()、MDN — prefers-reduced-motion。

関連記事: CSS ホログラムカード効果9選 — ステッカー・バッジ・チケット · css キャラクター アニメーション 9選 — かわいいキャラを動かすCSS · ペーパーカット デザインのLPパーツ9選、コピペOK

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。