マウスやタッチ操作に応じてサーチライトが動き、指定された文字が強調されるインタラクティブなエフェクトを実装しています。
マウスを動かすことでサーチライトが追従し、その部分だけが明るく照らされ、他の部分は暗くなります。
タッチスクリーンデバイスでもタッチ操作によって同様の効果が得られ、スマホやタブレットでも使いやすいデザインとなっています。
ルパン3世風をイメージしました
マウスを動かすと、サーチライトがマウスカーソルに追従します。
スマホやタブレットでは、画面をタッチして指でなぞると、サーチライトがその指の動きに合わせて移動します。
文字が見える!
document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById("mask"); const ctx = canvas.getContext("2d"); const container = document.getElementById("container"); // キャンバスのサイズを設定 canvas.width = container.offsetWidth; canvas.height = container.offsetHeight; // 初期状態 const radius = 30; // サーチライトの半径 const diameter = radius * 2; // サーチライトの直径 let x = Math.random() * (canvas.width + diameter) - radius; // 初期位置X(画面外から少し出た位置) let y = Math.random() * (canvas.height + diameter) - radius; // 初期位置Y(画面外から少し出た位置) let angle = Math.random() * Math.PI * 2; // ランダムな角度 let speed = 2; // 移動速度 let isMouseOver = false; // マウスオーバー中かどうか let mouseX = 0; // マウスのX座標 let mouseY = 0; // マウスのY座標 // サーチライトの円が画面外に出るかチェック(直径で判定) function isOutOfBounds(x, y) { return ( x < -diameter || x > canvas.width + diameter || y < -diameter || y > canvas.height + diameter ); } // ランダムな角度を決める function getRandomAngle() { return Math.random() * Math.PI * 2; } // マウスオーバー時の処理 container.addEventListener("mouseover", () => { isMouseOver = true; }); container.addEventListener("mouseout", () => { isMouseOver = false; }); // マウスの座標を更新 container.addEventListener("mousemove", (e) => { mouseX = e.clientX - container.getBoundingClientRect().left; mouseY = e.clientY - container.getBoundingClientRect().top; }); // タッチの座標を更新 container.addEventListener("touchmove", (e) => { const touch = e.touches[0]; mouseX = touch.clientX - container.getBoundingClientRect().left; mouseY = touch.clientY - container.getBoundingClientRect().top; e.preventDefault(); // スクロールやズームを防ぐ }); // サーチライト効果を描画 function draw() { // 全体を黒で塗りつぶし ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); // サーチライト効果(黄色の光) const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius * 2); gradient.addColorStop(0, "rgba(255, 255, 150, 0.8)"); // 中心部分(黄色) gradient.addColorStop(1, "rgba(0, 0, 0, 0.8)"); // 外側(暗く) // グラデーションを適用 ctx.globalCompositeOperation = "destination-out"; ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(x, y, radius * 2, 0, Math.PI * 2, false); ctx.fill(); // 描画モードを元に戻す ctx.globalCompositeOperation = "source-over"; // サーチライトが範囲外に出たら新しいランダムな位置と角度に設定 if (isOutOfBounds(x, y)) { x = Math.random() * (canvas.width + diameter) - radius; y = Math.random() * (canvas.height + diameter) - radius; angle = getRandomAngle(); // 新しい角度をランダムに決める } // マウスオーバー時はマウスに追従 if (isMouseOver) { x = mouseX; y = mouseY; } else { // 斜めに動かす(ランダム移動) x += Math.cos(angle) * speed; y += Math.sin(angle) * speed; } // アニメーションフレームを継続 requestAnimationFrame(draw); } // 描画を開始 draw(); });
<div id="container"> <div style="z-index: 1; position: relative;">文字が見える!</div> <canvas id="mask"></canvas> </div>
#container { position: relative; width: 50%; height: 200px; font-size: 4.0rem; font-weight: 900; text-align: center; color: #000000; background: #FFFFFF; overflow: hidden; user-select: none; display: flex; justify-content: center; align-items: center; margin: 0 auto; } #mask { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; } /*media Queries スマホサイズ(599px)以下で適応したいCSS - スマホのみ ---------------------------------------------------------------------------------------------------*/ @media print, screen and (max-width: 599px) { #container { width: 100%; } /*-- ここまで --*/ }