エフェクト
mouse-click

Mouse-click


デモ

ソースコード

/**
 * メッシュやリンクにホバーした際に Click が表示されるマウスエフェクト
 * Mouse effect that displays "Click" when hovering over meshes or links
 */
import { INode, utils } from "negl";

const distortion = { level: 500, max: 0.4 };
let $, _mouse;

function initMouseClick(negl) {
  const { mouse, config } = negl;
  _mouse = mouse;
  if (utils.isTouchDevices()) {
    return () => {};
  }

  const { current, target, delta, initial } = mouse;

  const circleInitial = {
    r: 40,
    fill: "#ffffff",
    fillOpacity: 0.3,
    textScale: 0,
    strokeWidth: 0,
    scale: 0.4,
    scaleX: 0.4,
    scaleY: 0.4,
    rotate: 0,
  };

  Object.assign(initial, circleInitial);
  Object.assign(current, circleInitial);
  Object.assign(target, circleInitial);
  Object.assign(delta, { scale: 1, fillOpacity: 0 });

  const svg = _createHTML(negl);
  const children = INode.qsAll(".mouse-child", svg);
  const outerCircle = children[0];
  const textSvg = children[1];
  const globalContainer = INode.getElement(config.$.globalContainer);
  const inlineStyle = _getInlineStyle();

  globalContainer.append(inlineStyle);
  globalContainer.append(svg);

  $ = {
    svg,
    textSvg,
    outerCircle,
    globalContainer,
  };

  bindEvents(negl);

  /**
   * ロード完了時に実行
   * Executed upon load completion
   * @description ユーザー定義メソッドのため実行必須ではありません。Not mandatory to execute as it is a user-defined method.
   */
  return function onload() {
    if (utils.isTouchDevices()) return;
    const intervalId = window.setInterval(() => {
      if (!mouse.isUpdate()) return;
      $.svg.style.opacity = 1;
      clearInterval(intervalId);
    }, 300);
  };
}

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

  // メッシュに入った時
  // When entering the mesh
  hook.on(hook.MOUSE_MESH_ENTER, enter);
  // メッシュから出た時
  // When leaving the mesh
  hook.on(hook.MOUSE_MESH_LEAVE, leave);
  hook.on(hook.MOUSE_MESH_CLICK, click);
  // リサイズした時
  // When resizing
  hook.on(hook.RESIZE, _resize);

  // 画面を離れる時
  // When leaving the screen
  hook.on(
    hook.T_BEGIN,
    () => {
      $.svg.style.opacity = 0;
      clearTimeout(timerId);
      leave();
    },
    { priority: 1000 }
  );

  // 画面に戻ってきた時
  // When returning to the screen
  hook.on(
    hook.T_END,
    () => {
      $.svg.style.opacity = 1;
    },
    { priority: 1000 }
  );

  // リンクにバインド
  // 特定の要素に入ったタイミングでメッシュのスケールを変化させる
  // Bind to links
  // Change the scale of the mesh when entering specific elements
  let targets;
  function _bindDOMEvents() {
    targets = [...document.querySelectorAll("a")];
    targets.forEach((el) => el.addEventListener("mouseenter", enter));
    targets.forEach((el) => el.addEventListener("mouseleave", leave));
  }

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

  _bindDOMEvents();
  // ページ遷移が開始された時
  // When page transition starts
  hook.on(hook.T_BEGIN, _destroyDOMEvents);
  // ページ遷移が終了する時
  // When page transition ends
  hook.on(hook.T_END, _bindDOMEvents);
}

/**
 * svgの作成
 * Creation of the svg
 * @description ユーザー独自メソッド(User custom method)
 */
function _createHTML({ mouse, viewport }) {
  const { current } = mouse;

  return INode.htmlToEl(`
    <svg
      class="mouse-viewport"
      width="${viewport.width}"
      height="${viewport.height}"
      preserveAspectRatio="none meet"
      style="opacity: 0; transition: opacity 1s;"
      viewBox="0 0 ${viewport.width} ${viewport.height}"
      >
      <g class="mouse-wrapper">
        <circle
          class="mouse-child outer"
          r="${current.r}"
          cx="${current.x}"
          cy="${current.y}"
          fill="${current.fill}"
          fill-opacity="${current.fillOpacity}"
          stroke="${current.fill}"
          stroke-width="${current.strokeWidth}"
          style="transform-origin: ${current.x}px ${current.y}px"
        ></circle>
        <text
          class="mouse-child"
          text-anchor="middle"
          dominant-baseline="central"
          x="${current.x}"
          y="${current.y}"
          font-family="Poppins"
          font-size="16"
          font-weight="700"
          letter-spacing="2"
          fill="#ffffff">
          Click
        </text>
      </g>
    </svg>
    `);
}

/**
 * svgのスタイルの作成
 * Creation of the svg style
 * @description ユーザー独自メソッド(User custom method)
 */
function _getInlineStyle() {
  return INode.htmlToEl(`
      <style>
      .mouse-viewport {
        position: fixed;
        top: 0;
        left: 0;
        height: 100vh;
        width: 100%;
        z-index: 99999;
        pointer-events: none;
      }
      </style>
  `);
}

/**
 * マウスが動いた際に実行
 * Executed when the mouse moves
 */
function move(mouse) {
  if (utils.isTouchDevices()) return;
  const { current, target, delta, speed } = mouse;
  delta.scale = target.scale - current.scale;
  delta.fillOpacity = target.fillOpacity - current.fillOpacity;

  current.scale += delta.scale * speed;
  current.fillOpacity += delta.fillOpacity * speed;

  let distort =
    Math.sqrt(Math.pow(delta.x, 2) + Math.pow(delta.y, 2)) / distortion.level;
  distort = Math.min(distort, distortion.max);
  current.scaleX = (1 + distort) * current.scale;
  current.scaleY = (1 - distort) * current.scale;

  current.rotate = (Math.atan2(delta.y, delta.x) / Math.PI) * 180;

  delta.textScale = target.textScale - current.textScale;
  current.textScale += delta.textScale * speed;

  // スタイルの変更
  // Style changes
  $.textSvg.style.transformOrigin = `${current.x}px ${current.y}px`;
  $.textSvg.setAttribute("x", target.x.toString());
  $.textSvg.setAttribute("y", target.y.toString());
  $.textSvg.style.transform = `scale(${current.textScale})`;

  $.outerCircle.style.transformOrigin = `${current.x}px ${current.y}px`;
  $.outerCircle.setAttribute("cx", current.x.toString());
  $.outerCircle.setAttribute("cy", current.y.toString());
  $.outerCircle.setAttribute("fill-opacity", current.fillOpacity.toString());

  const rotate = `rotate(${current.rotate}deg)`;
  const scale = `scale(${current.scaleX}, ${current.scaleY})`;

  $.outerCircle.style.transform = `${rotate} ${scale}`;
}

/**
 * 画面のリサイズ時に行う処理
 * Process to be performed when resizing the screen
 */
function _resize(viewport) {
  if (utils.isTouchDevices()) {
    return;
  }
  $.svg.setAttribute("width", viewport.width.toString());
  $.svg.setAttribute("height", viewport.height.toString());
  $.svg.setAttribute("viewBox", `0 0 ${viewport.width} ${viewport.height}`);
}

/**
 * レイキャスティング対象のメッシュに入った際に実行する処理
 * Process executed when entering a mesh targeted by raycasting
 */
function enter() {
  Promise.resolve().then(() => {
    _mouse.setTarget({
      scale: 1,
      textScale: 1,
      fillOpacity: 0.4,
    });
    document.body.style.cursor = "pointer";
  });
}

/**
 * レイキャスティング対象のメッシュから出た際に実行する処理
 * Process executed when leaving a mesh targeted by raycasting
 */
function leave() {
  _mouse.setTarget({
    scale: _mouse.initial.scale,
    textScale: 0,
    fillOpacity: _mouse.initial.fillOpacity,
  });
  document.body.style.cursor = "";
}

let timerId;
function click() {
  clearTimeout(timerId);

  _mouse.setTarget({
    scale: 0.8,
    textScale: 0.8,
  });

  timerId = setTimeout(() => {
    _mouse.setTarget({
      scale: 1,
      textScale: 1,
    });
  }, 100);
}

export default initMouseClick;

利用方法

⚠️

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

index.html

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

使用画像・動画

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