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>
使用画像・動画
- こちらを参考にダウンロードした各ファイルを配置してください。