エフェクト
mouse-pic

Mouse-pic


デモ

ソースコード

import { isMobile } from "#/parts/helper/isMobile";

let imageOb;
/**
 * 初期化
 * Initialization
 */
async function initMousePic(negl, img) {
  const { world, hook, utils, mouse, viewport } = negl;
  if (utils.isTouchDevices()) return;

  imageOb = await world.createOb(`
    <img
      data-webgl="mouse-pic"
      data-layers="1"
      data-tex-1="${img}"
      style="width: 600px;top:0;"
    />
    `);

  imageOb.fixed = true;
  imageOb.mesh.position.z = 50;
  // 初期化時はスケールを0にして非表示
  // Set scale to 0 on initialization to make it invisible
  imageOb.mesh.scale.setScalar(0);

  // マウス位置によってメッシュを動かす
  // Move the mesh based on mouse position
  function bindMove() {
    move(mouse, viewport, utils);
  }
  // マウスにメッシュを追従
  // Mesh follows the mouse
  hook.on(hook.RENDER, bindMove);

  bindDOMEvents(negl);

  hook.on(hook.T_BEGIN, remainImageObInScene, { priority: 1000 });

  hook.on(hook.T_END, bindDOMEvents.bind(null, negl));
}

function remainImageObInScene({ current }) {
  // imageObをシーン内に残す
  // current.osに入っているObオブジェクトが自動的にシーンから削除される。
  // Keep imageOb in the scene
  // The Ob object in current.os is automatically removed from the scene.
  const idx = current.os.indexOf(imageOb);
  current.os.splice(idx, 1);
}

function bindDOMEvents({ hook, loader }) {
  // 特定の要素に入ったタイミングでメッシュのスケールを変化させる
  // Change the mesh scale when entering specific elements
  const targets = [...document.querySelectorAll("[data-mouse-pic]")];
  async function enter({ currentTarget }) {
    const url = currentTarget.dataset.mousePic;
    const tex = await loader.loadTex(url);
    await mouseenter(tex);
  }

  // イベントにバインド
  // Bind to events
  targets.forEach((el) => el.addEventListener("mouseenter", enter));
  targets.forEach((el) => el.addEventListener("mouseleave", mouseleave));

  function destroyDOMEvents() {
    mouseleave();
    targets.forEach((el) => el.removeEventListener("mouseenter", enter));
    targets.forEach((el) => el.removeEventListener("mouseleave", mouseleave));
  }

  // 画面遷移の開始時に実行
  // イベントを登録するDOMは画面ごとに異なるため、現在ページのイベントは破棄する
  // 破棄しないと監視イベントが画面遷移ごとにどんどん多くなっていく(メモリリークする)
  // Execute at the start of screen transition
  // Since the DOM to register events is different for each screen, discard the events of the current page
  // If not discarded, the number of monitoring events will increase with each screen transition (causing memory leaks)
  hook.on(hook.T_BEGIN, destroyDOMEvents, { once: true });
}

/**
 * マウスが動いた際に実行
 * Executed when the mouse moves
 */
const offset = { x: 200, y: 0.4 };
function move(mouse, viewport, utils) {
  // 数値調整
  // Numerical adjustment
  const mousePos = mouse.getMapPos(viewport.width, viewport.height);
  imageOb.mesh.position.x = mousePos.x - Math.sign(mousePos.x) * offset.x;
  imageOb.mesh.position.y = mousePos.y - mousePos.y * offset.y;
  if (scale.current !== scale.target) {
    scale.current = utils.lerp(scale.current, scale.target, 0.4);
    imageOb.mesh.scale.set(
      imageOb.scale.width * scale.current,
      imageOb.scale.height * scale.current,
      imageOb.scale.depth * scale.current
    );
  }
}

let scale = { current: 0, target: 0 };
/**
 * レイキャスティング対象のメッシュに入った際に実行する処理
 * Process executed when entering a mesh targeted by raycasting
 */
let currentTex;
async function mouseenter(tex) {
  if (!imageOb || isMobile()) return;

  currentTex = imageOb.uniforms.tex1.value = tex;

  if (tex.source.data instanceof HTMLVideoElement) {
    await tex.source.data.play();
  }

  scale.target = 1;
}

/**
 * レイキャスティング対象のメッシュから出た際に実行する処理
 * Process executed when exiting a mesh targeted by raycasting
 */
async function mouseleave() {
  if (!imageOb) return;

  if (currentTex instanceof HTMLVideoElement) {
    currentTex.source.data.pause();
    currentTex = null;
  }

  scale.target = 0;
}

export default initMousePic;

利用方法

⚠️

ダウンロードしたコードをプロジェクトに配置し、以下のコードを記述してください。

index.html

<div class="mouse">
  <img
    data-webgl="template"
    data-tex-1="/sample1.jpg"
    data-mouse-pic="/sample1.jpg"
  />
  <img
    data-webgl="template"
    data-tex-1="/sample2.jpg"
    data-mouse-pic="/sample2.jpg"
  />
  <img
    data-webgl="template"
    data-tex-1="/sample3.jpg"
    data-mouse-pic="/sample3.jpg"
  />
  <img
    data-webgl="template"
    data-tex-1="/sample4.jpg"
    data-mouse-pic="/sample4.jpg"
  />
</div>

補足

mouse-pic エフェクトを使用して作成しています。 シェーダーコードの詳細はダウンロード資材をご覧ください。

使用画像・動画

  • こちらを参考にダウンロードした各ファイルを配置してください。