WebGLホバーエフェクトをサイトに取り入れてみる - Three.jsでインタラクティブな体験を実現
2025.06.25
このサイトはアフィリエイト広告を利用しています。
はじめに
現代のWebサイトでは、ユーザーエクスペリエンスを向上させるためにインタラクティブなエフェクトが重要な役割を果たしています。特にWebGLホバーエフェクトは、従来のCSSアニメーションでは実現困難な高度な視覚表現を可能にし、サイト全体に動的で印象的な体験をもたらします。
WebGLは3Dグラフィックスをブラウザ上で高速に描画する技術であり、Three.jsのようなライブラリと組み合わせることで、開発者は比較的簡単に複雑なエフェクトを実装できます。本記事では、WebGLホバーエフェクトの基本的な実装から応用的なテクニックまで、実際のコード例を交えながら詳しく解説します。
前提知識
本記事では、HTML、CSS、JavaScriptの基本的な知識とThree.jsの基礎的な理解があることを前提としています。
WebGLホバーエフェクトの魅力的な活用例5選
WebGLホバーエフェクトは様々な形で実装され、サイトの個性を演出する重要な要素となっています。以下に代表的な活用例を示します。
1. ディストーション(歪み)エフェクト
画像ホバー時にディスプレイスメント画像を使用して歪みを作り出すエフェクトです。異なるディスプレイスメント画像を使用することで、多様な視覚効果を実現できます。
2. ページ全体の明度変化エフェクト
ホバー対象の画像だけでなく、ページ全体の明度を変化させるエフェクトが注目されています。この手法により、より印象的で統一感のある視覚体験を提供できます。
3. グリッチアニメーション
意図的にノイズや歪みを加えることで、エラーやバグのような視覚効果を再現するエフェクトです。Canvasのピクセル単位での操作により、細かい制御が可能です。
4. 環境マッピング効果
金属やガラスのような反射性表面を表現するために使用され、周辺環境がマッピングされる質感を実現します。
5. レイキャストによる精密な当たり判定
Three.jsのレイキャスト機能を活用することで、3Dオブジェクトとマウスの正確な交差判定を行い、より精密なホバーエフェクトを実装できます。
最小限のHTML構造とCSS設定
WebGLホバーエフェクトを実装するための基本的なHTML構造は非常にシンプルです。以下の例では、Three.jsを使用した最小限のセットアップを示します。
<!DOCTYPE html>
<html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>WebGL Hover Effect</title> <style> body { margin: 0; padding: 0; overflow: hidden; font-family: Arial, sans-serif; }
#canvas-container { position: relative; width: 100vw; height: 100vh; }
canvas { display: block; position: absolute; top: 0; left: 0; }
.image-overlay { position: absolute; width: 300px; height: 200px; cursor: pointer; z-index: 100; } </style> </head> <body> <div id="canvas-container"> <div class="image-overlay" data-image="image1.jpg"></div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="app.js"></script> </body></html>
パフォーマンス最適化
Canvasエレメントのスタイリングでdisplay: block
を指定することで、余分な空白を除去し、レンダリングパフォーマンスを向上させることができます。
Three.jsでシンプルなワープエフェクト実装
基本的なThree.jsセットアップから始めて、ホバー時にワープエフェクトを適用する実装を行います。
実装サンプル
import * as THREE from "three";
class WebGLHoverEffect { constructor(container) { this.container = container; this.mouse = new THREE.Vector2(); this.raycaster = new THREE.Raycaster();
this.init(); this.setupEventListeners(); this.animate(); }
init() { // シーンの初期化 this.scene = new THREE.Scene();
// カメラの設定 this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.z = 5;
// レンダラーの設定 this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.container.appendChild(this.renderer.domElement);
// 基本的なジオメトリとマテリアルの作成 this.createMesh(); }
createMesh() { const geometry = new THREE.PlaneGeometry(2, 2);
// カスタムシェーダーマテリアル const material = new THREE.ShaderMaterial({ uniforms: { u_time: { value: 0 }, u_mouse: { value: new THREE.Vector2() }, u_hover: { value: 0 }, }, vertexShader: ` varying vec2 vUv; uniform float u_time; uniform float u_hover;
void main() { vUv = uv; vec3 pos = position;
// ホバー時のワープ効果 pos.z += sin(pos.x * 10.0 + u_time) * u_hover * 0.1;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); } `, fragmentShader: ` varying vec2 vUv; uniform float u_hover;
void main() { vec3 color = vec3(0.2, 0.4, 0.8); color = mix(color, vec3(0.8, 0.2, 0.4), u_hover);
gl_FragColor = vec4(color, 1.0); } `, });
this.mesh = new THREE.Mesh(geometry, material); this.scene.add(this.mesh); }
setupEventListeners() { const canvas = this.renderer.domElement;
canvas.addEventListener("mousemove", event => { this.handleMouseMove(event); });
canvas.addEventListener("mouseenter", () => { this.animateHover(true); });
canvas.addEventListener("mouseleave", () => { this.animateHover(false); }); }
handleMouseMove(event) { const rect = this.renderer.domElement.getBoundingClientRect();
// マウス座標を-1から+1の範囲に正規化 this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// レイキャストの更新 this.raycaster.setFromCamera(this.mouse, this.camera); const intersects = this.raycaster.intersectObjects(this.scene.children);
if (intersects.length > 0) { this.mesh.material.uniforms.u_mouse.value = this.mouse; } }
animateHover(isHovering) { const targetValue = isHovering ? 1 : 0;
const animate = () => { const current = this.mesh.material.uniforms.u_hover.value; const step = (targetValue - current) * 0.1;
this.mesh.material.uniforms.u_hover.value += step;
if (Math.abs(targetValue - current) > 0.01) { requestAnimationFrame(animate); } };
animate(); }
animate() { requestAnimationFrame(() => this.animate());
this.mesh.material.uniforms.u_time.value += 0.016; this.renderer.render(this.scene, this.camera); }}
// 初期化const container = document.getElementById("canvas-container");new WebGLHoverEffect(container);
シェーダーの理解
WebGLホバーエフェクトの核心は、頂点シェーダーとフラグメントシェーダーの組み合わせにあります。これらを理解することで、より高度なエフェクトの実装が可能になります。
画像にグリッチエフェクトを適用する方法
グリッチエフェクトは、意図的なノイズや歪みを加えることで視覚的なインパクトを与える手法です。以下の実装では、画像テクスチャにグリッチエフェクトを適用します。
実装サンプル
Loading...
class GlitchEffect { constructor(imageUrl, container) { this.imageUrl = imageUrl; this.container = container; this.glitchIntensity = 0;
this.init(); }
init() { this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this.renderer = new THREE.WebGLRenderer(); this.renderer.setSize(window.innerWidth, window.innerHeight); this.container.appendChild(this.renderer.domElement);
this.loadTexture(); }
loadTexture() { const loader = new THREE.TextureLoader(); loader.load(this.imageUrl, texture => { this.createGlitchMaterial(texture); this.animate(); }); }
createGlitchMaterial(texture) { const material = new THREE.ShaderMaterial({ uniforms: { u_texture: { value: texture }, u_time: { value: 0 }, u_glitchIntensity: { value: 0 }, u_resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight), }, }, vertexShader: ` varying vec2 vUv;
void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform sampler2D u_texture; uniform float u_time; uniform float u_glitchIntensity; uniform vec2 u_resolution; varying vec2 vUv;
// ランダム関数 float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); }
void main() { vec2 uv = vUv;
// グリッチエフェクトの適用 float glitch = random(vec2(floor(uv.y * 100.0), u_time)) * u_glitchIntensity;
// RGBチャンネルの分離 float r = texture2D(u_texture, uv + vec2(glitch * 0.01, 0.0)).r; float g = texture2D(u_texture, uv).g; float b = texture2D(u_texture, uv - vec2(glitch * 0.01, 0.0)).b;
// ノイズラインの追加 float noise = random(vec2(0.0, floor(uv.y * 80.0) + u_time)); if (noise > 0.98) { r = 1.0; g = 1.0; b = 1.0; }
gl_FragColor = vec4(r, g, b, 1.0); } `, });
const geometry = new THREE.PlaneGeometry(2, 2); this.mesh = new THREE.Mesh(geometry, material); this.scene.add(this.mesh);
this.setupHoverEvents(); }
setupHoverEvents() { this.renderer.domElement.addEventListener("mouseenter", () => { this.animateGlitch(1.0); });
this.renderer.domElement.addEventListener("mouseleave", () => { this.animateGlitch(0.0); }); }
animateGlitch(targetIntensity) { const animate = () => { const step = (targetIntensity - this.glitchIntensity) * 0.1; this.glitchIntensity += step;
this.mesh.material.uniforms.u_glitchIntensity.value = this.glitchIntensity;
if (Math.abs(targetIntensity - this.glitchIntensity) > 0.01) { requestAnimationFrame(animate); } };
animate(); }
animate() { requestAnimationFrame(() => this.animate());
this.mesh.material.uniforms.u_time.value += 0.016; this.renderer.render(this.scene, this.camera); }}
カスタマイズ可能なパラメータ設定解説
WebGLホバーエフェクトの魅力は、様々なパラメータを調整することで独自の表現を作り出せることです。以下に主要なカスタマイズポイントを示します。
エフェクトの強度調整
パラメータ | 説明 | 推奨値 | 効果 |
---|---|---|---|
glitchIntensity | グリッチの強さ | 0.0 - 1.0 | 値が大きいほど歪みが強くなる |
warpAmount | ワープの振幅 | 0.1 - 0.5 | 3D変形の程度を制御 |
transitionSpeed | アニメーション速度 | 0.05 - 0.2 | ホバー時の変化速度 |
noiseScale | ノイズの細かさ | 10.0 - 100.0 | テクスチャノイズの解像度 |
タイミング制御
class EffectController { constructor() { this.config = { // アニメーション設定 hoverDelay: 0.1, // ホバー開始遅延(秒) exitDelay: 0.2, // ホバー終了遅延(秒) easeType: "easeOutCubic", // イージングタイプ
// 視覚効果設定 colorShift: 0.5, // 色相変化の強度 brightness: 1.2, // 明度調整 saturation: 1.5, // 彩度調整
// パフォーマンス設定 maxFPS: 60, // 最大フレームレート pixelRatio: window.devicePixelRatio, // デバイスピクセル比 }; }
updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; }}
パフォーマンスバランス
エフェクトの品質とパフォーマンスのバランスを取るために、デバイスの性能に応じてパラメータを動的に調整することを推奨します。
レスポンシブ対応
class ResponsiveWebGLEffect { constructor() { this.breakpoints = { mobile: 768, tablet: 1024, desktop: 1440, };
this.setupResponsiveConfig(); this.setupResizeHandler(); }
setupResponsiveConfig() { const width = window.innerWidth;
if (width < this.breakpoints.mobile) { this.config = { quality: "low", maxParticles: 50, shadowMapSize: 512, }; } else if (width < this.breakpoints.tablet) { this.config = { quality: "medium", maxParticles: 100, shadowMapSize: 1024, }; } else { this.config = { quality: "high", maxParticles: 200, shadowMapSize: 2048, }; } }
setupResizeHandler() { let resizeTimeout;
window.addEventListener("resize", () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { this.handleResize(); }, 250); }); }
handleResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); this.setupResponsiveConfig(); }}
実際のサイトに組み込む際の注意点
WebGLホバーエフェクトを実際のプロダクションサイトに組み込む際には、いくつかの重要な考慮事項があります。
パフォーマンス最適化
モバイルデバイスでの制限
モバイルデバイスでは、バッテリー消費やGPUの制限により、WebGLエフェクトのパフォーマンスが大幅に低下する可能性があります。
class PerformanceManager { constructor() { this.performanceMonitor = { frameCount: 0, lastTime: performance.now(), averageFPS: 60, };
this.setupPerformanceMonitoring(); }
setupPerformanceMonitoring() { const monitor = () => { const currentTime = performance.now(); const deltaTime = currentTime - this.performanceMonitor.lastTime;
this.performanceMonitor.frameCount++;
// 1秒ごとにFPSを計算 if (deltaTime >= 1000) { this.performanceMonitor.averageFPS = (this.performanceMonitor.frameCount * 1000) / deltaTime;
this.adjustQuality();
this.performanceMonitor.frameCount = 0; this.performanceMonitor.lastTime = currentTime; }
requestAnimationFrame(monitor); };
monitor(); }
adjustQuality() { if (this.performanceMonitor.averageFPS < 30) { // 低性能デバイスへの対応 this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1)); this.renderer.shadowMap.enabled = false; } else if (this.performanceMonitor.averageFPS > 50) { // 高性能デバイスでの品質向上 this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.shadowMap.enabled = true; } }}
アクセシビリティ配慮
class AccessibilityManager { constructor(effectInstance) { this.effectInstance = effectInstance; this.setupAccessibilityFeatures(); }
setupAccessibilityFeatures() { // モーション設定の尊重 const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" );
if (prefersReducedMotion.matches) { this.effectInstance.disableAnimations(); }
prefersReducedMotion.addEventListener("change", e => { if (e.matches) { this.effectInstance.disableAnimations(); } else { this.effectInstance.enableAnimations(); } });
// キーボードナビゲーション対応 this.setupKeyboardControls(); }
setupKeyboardControls() { document.addEventListener("keydown", event => { if (event.key === "Tab") { // フォーカス時にホバーエフェクトを適用 const focusedElement = document.activeElement; if (focusedElement.classList.contains("webgl-hover-target")) { this.effectInstance.triggerHoverEffect(focusedElement); } } }); }}
SEOとクローラー対応
WebGLエフェクトは検索エンジンのクローラーによって認識されないため、コンテンツのアクセシビリティを確保する必要があります。
<!-- フォールバック用のコンテンツ -->
<div class="webgl-container" role="img" aria-label="インタラクティブな製品画像"> <canvas id="webgl-canvas"></canvas>
<!-- WebGL非対応時のフォールバック --> <noscript> <img src="fallback-image.jpg" alt="製品画像" /> </noscript>
<!-- スクリーンリーダー向けの説明 --> <div class="sr-only"> この画像は、マウスホバーまたはタッチで視覚効果が変化します。 効果を無効にする場合は、ブラウザの設定でモーション効果を減らしてください。 </div></div>
テスト環境
様々なデバイス・ブラウザでの動作確認を必ず行い、特に古いデバイスや低性能デバイスでの動作を確認してください。
まとめ
WebGLホバーエフェクトは、従来のWeb技術では表現困難な高度な視覚効果を実現し、ユーザーエクスペリエンスを大幅に向上させる強力な手法です。Three.jsを基盤とした実装により、比較的簡単に複雑なエフェクトを作成できます。
重要なポイントとして、効果的なWebGLホバーエフェクトの実装には以下の要素が必要です:
- 適切なパフォーマンス管理: デバイス性能に応じた動的な品質調整
- アクセシビリティ配慮: モーション設定の尊重とキーボードナビゲーション対応
- レスポンシブ設計: 様々な画面サイズとデバイスへの対応
- フォールバック対応: WebGL非対応環境での代替手段の提供
レイキャストによる精密な当たり判定やグリッチエフェクトなどの応用技術を組み合わせることで、さらに印象的で独創的なエフェクトを実現できます。ただし、パフォーマンスとユーザビリティのバランスを常に意識し、すべてのユーザーにとってアクセシブルな実装を心がけることが重要です。
出典
公式リソース(Official Resources)