マウスの位置に合わせて要素を動かす(HTML/CSS/JS)
前書き
マウスの位置に合わせて要素を動かす方法を掲載しています。
目次
- 複数の要素を、差をつけて動かす
- マウスを追従、変化させる
- three.jsのカメラを動かす
- 参考
複数の要素を、差をつけて動かす
下記ページを参考にしています。
遠近感を味わえるマウスインタラクションを作ってみた!
当ページでは、枠線内でマウスを動かした時に、マウスの位置に合わせて、下記を動かしています。
また、スマホ(touchイベント)でも動く様にしています。
- translateX
- translateY
- rotateX
- rotateY
い
ろ
は
に
ほ
コード
<div class="wrap js-wrap">
<div class="item item-01 js-item" data-acc="1, 1, -1, -1">
<div class="interior interior-01">い</div>
</div>
<div class="item item-02 js-item" data-acc="2, 2, -2, -2">
<div class="interior interior-02">ろ</div>
</div>
<div class="item item-03 js-item" data-acc="2, 2, -2, -2">
<div class="interior interior-03">は</div>
</div>
<div class="item item-04 js-item" data-acc="3, 3, -3, -3">
<div class="interior interior-04">に</div>
</div>
<div class="item item-05 js-item" data-acc="3, 3, -3, -3">
<div class="interior interior-05">ほ</div>
</div>
</div>
<!-- data-acc="translateX, translateY, rotateX, rotateY" -->
.wrap{
position: relative;
perspective: 50em; //奥行きを出す
}
.item {
position: absolute;
transition: all .1s; //.1s かけて変化する
&-01 {
z-index: 5; top: 50%; left: 50%;
}
&-02 {
z-index: 4; top: 50%; left: 35%;
}
&-03 {
z-index: 3; top: 50%; left: 65%;
}
&-04 {
z-index: 2; top: 50%; left: 20%;
}
&-05 {
z-index: 1; top: 50%; left: 80%;
}
}
.interior {
transform: translate(-50%, -50%); //中央に寄せる
//装飾
width: 1.5em; height: 1.5em; color: #fff; background: #845080;
}
//表示場所
const wrap = document.querySelector('.js-wrap');
//動かす要素
const itemNode = document.querySelectorAll('.js-item');
const itemArray = Array.from(itemNode);
//加速度
const itemAcc = itemArray.map((item) => {
const acc = item.dataset.acc.split(',');
const accTsX = Number(acc[0]); //translateX
const accTsY = Number(acc[1]); //translate
const accRtX = Number(acc[2]); //rotateX
const accRtY = Number(acc[3]); //rotateY
return { accTsX, accTsY, accRtX ,accRtY };
});
//ポインターの位置、座標
let pointerX = 0;
let pointerY = 0;
let x = 0;
let y = 0;
//最小値、最大値
const minmax = (num) => {
return Math.min( 0.5, Math.max(-0.5,num)); //-0.5以上0.5以下
}
const coordinate = () => {
//表示場所の位置
const wrapReact = wrap.getBoundingClientRect();
//ポインターが表示場所のどの位置にあるか。中心を(0,0)とする為に0.5引く
x = (pointerX - wrapReact.left) / wrapReact.width - 0.5;
y = (pointerY - wrapReact.top) / wrapReact.height - 0.5;
//最小値、最大値の確認(touchイベント用)
x = minmax(x);
y = minmax(y);
}
//マウスの位置
wrap.addEventListener('mousemove', e => {
pointerX = e.clientX;
pointerY = e.clientY;
coordinate();
});
//要素のstyle属性(座標 x 30(微調整)x 加速度)
const styling = () => {
itemNode.forEach((item, index) => {
const tsX = x * 30 * itemAcc[index].accTsX + "%";
const tsY = y * 30 * itemAcc[index].accTsY + "%";
const rtX = y * 30 * itemAcc[index].accRtX + "deg";
const rtY = x * 30 * itemAcc[index].accRtY + "deg";
item.style.transform = "translateX(" + tsX + ") translateY(" + tsY + ") rotateX( " + rtX + ") rotateY(" + rtY + ")";
});
}
//表示場所にマウスが入ったら
let tick;
wrap.addEventListener('mouseenter', e => {
tick = () => {
styling();
requestAnimationFrame( tick );
}
requestAnimationFrame( tick );
});
//表示場所からマウスが出たら
wrap.addEventListener('mouseleave', e => {
tick = () => {
cancelAnimationFrame( tick )
}
});
//スマホ対応(touchイベント)------------------
//触れている位置
wrap.addEventListener('touchmove', e => {
pointerX = e.touches[0].clientX;
pointerY = e.touches[0].clientY;
coordinate();
});
//スクロールの制御
const handleTouchMove = (event) => {
event.preventDefault();
}
//表示場所に指が触れたら
wrap.addEventListener('touchstart', e => {
document.addEventListener('touchmove', handleTouchMove, { passive: false }); //スクロールの禁止
tick = () => {
styling();
requestAnimationFrame( tick );
}
requestAnimationFrame( tick );
});
//指が離れたら
wrap.addEventListener('touchend', e => {
document.removeEventListener('touchmove', handleTouchMove, { passive: false }); //スクロールの許可
tick = () => {
cancelAnimationFrame( tick )
}
});
マウスを追従、変化させる
下記ページを参考にしています。
イケてるマウスカーソルを簡単に実装する
当ページでは、枠線内でマウスを動かした時に、マウスの位置に合わせて、要素を動かしています。
また、カーソルも変えています。
スマホ(touchイベント)でも動く様にしています。
い
ろ
は
<div class="wrap js-wrap-b">
<!-- マウスを追従する要素 -->
<div class="item item-01 js-item-b">
<div class="interior interior-01"></div>
</div>
<div class="item item-02 js-item-b">
<div class="interior interior-02"></div>
</div>
<div class="item item-03 js-item-b">
<div class="interior interior-03"></div>
</div>
<!-- 点線の枠 -->
<div class="area area-01 js-area" data-isin="isin01">い</div>
<div class="area area-02 js-area" data-isin="isin02">ろ</div>
<div class="area area-03 js-area" data-isin="isin03">は</div>
</div>
.wrap{
cursor: none; //カーソルを非表示
}
.item{
pointer-events: none; //ポインターに反応しないようにする
position: fixed; top: 0; left: 0; opacity: 0;
&-01{
z-index: 103; transition: opacity .2s; //遅れることなく追従する(透明度の変化にだけ時間をかける)
}
&-02{
z-index: 102; transition: all .2s; //.2s かけて追従する
}
&-03{
z-index: 101; transition: all .4s; //.4s かけて追従する
}
&.active{
opacity: 1;
}
}
.interior{
transform: translate(-50%, -50%); //中央に寄せる
transition: all .2s; //点線の枠に入った時、.2sかけて変化させる
//装飾 初め
border-radius: 50%;
&-01{
width: 2em; height: 2em; background: #845080;
&::before{
content: "";
}
}
&-02{
width: 3em; height: 3em; background: #e7d4e3;
}
&-03{
width: 4em; height: 4em; border: 1px solid #a675a5;
}
}
.isin01 .interior{
//装飾 点線の枠に入っている時1
}
.isin02 .interior{
//装飾 点線の枠に入っている時2
}
.isin03 .interior{
//装飾 点線の枠に入っている時3
}
//表示場所
const wrap = document.querySelector('.js-wrap-b');
//要素
const itemNode = document.querySelectorAll('.js-item-b');
//枠
const areaNode = document.querySelectorAll('.js-area');
//追従させる------------------
//ポインターの位置、座標
let pointerX = 0;
let pointerY = 0;
let x = 0;
let y = 0;
//マウスの位置
wrap.addEventListener('mousemove', e => {
pointerX = e.clientX;
pointerY = e.clientY;
});
//要素のstyle属性
const styling = () => {
itemNode.forEach((item) => {
const tsX = pointerX + "px";
const tsY = pointerY + "px";
item.style.transform = "translateX(" + tsX + ") translateY(" + tsY + ")";
});
}
//表示場所にマウスが入ったら1
let tick;
wrap.addEventListener('mouseenter', e => {
tick = () => {
styling();
requestAnimationFrame( tick );
}
requestAnimationFrame( tick );
});
//表示場所からマウスが出たら1
wrap.addEventListener('mouseleave', e => {
tick = () => {
cancelAnimationFrame( tick )
}
});
//変化させる------------------
//表示場所にマウスが入ったら2
wrap.addEventListener('mouseenter', e => {
itemNode.forEach((item) => {
item.classList.add("active");
});
});
//表示場所からマウスが出たら2
wrap.addEventListener('mouseleave', e => {
itemNode.forEach((item) => {
item.classList.remove("active");
});
});
//点線の枠
areaNode.forEach((area) => {
//data属性を取得
const isInClass = area.dataset.isin;
//点線の枠にマウスが入ったら
area.addEventListener('mouseenter', e => {
itemNode.forEach((item) => {
item.classList.add(isInClass);
});
});
//点線の枠からマウスが出たら
area.addEventListener('mouseleave', e => {
itemNode.forEach((item) => {
item.classList.remove(isInClass);
});
});
});
//一部スマホ対応(touchイベント)------------------
//触れている位置
wrap.addEventListener('touchmove', e => {
pointerX = e.touches[0].clientX;
pointerY = e.touches[0].clientY;
});
//スクロールの制御
const handleTouchMove = (event) => {
event.preventDefault();
}
//表示場所に指が触れたら1
wrap.addEventListener('touchstart', e => {
document.addEventListener('touchmove', handleTouchMove, { passive: false }); //スクロールの禁止
tick = () => {
styling();
requestAnimationFrame( tick );
}
requestAnimationFrame( tick );
});
//指が離れたら1
wrap.addEventListener('touchend', e => {
document.removeEventListener('touchmove', handleTouchMove, { passive: false }); //スクロールの許可
tick = () => {
cancelAnimationFrame( tick )
}
});
//表示場所に指が触れたら2
wrap.addEventListener('touchstart', e => {
itemNode.forEach((item) => {
item.classList.add("active");
});
});
//指が離れたら2
wrap.addEventListener('touchend', e => {
itemNode.forEach((item) => {
item.classList.remove("active");
});
});
three.jsのカメラを動かす
下記ページを参考に、マウスの動きに合わせて、カメラが物体の周囲を回るようにしています。
スマホ(touchイベント)でも動く様にしています。
Three.jsのカメラの制御
<div class="wrap js-wrap-c">
<canvas class="canvas"></canvas>
</div>
<script src="./three_r127/build/three.min.js"></script>
.wrap{
position: relative; padding: 50% 0 0; overflow: hidden;
}
.canvas{
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
}
// サイズ------------------
const wrap = document.querySelector(".js-wrap-c");
let wrapWidth = wrap.clientWidth;
let wrapHeight = wrap.clientHeight;
// レンダラー------------------
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector(".js-canvas")
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(wrapWidth, wrapHeight);
// シーン------------------
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf5edf3 ); //背景色
scene.fog = new THREE.Fog(0xf5edf3, 500, 2200); //フォグ
// カメラ------------------
const camera = new THREE.PerspectiveCamera(45, wrapWidth / wrapHeight);
camera.position.set(0, 0, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 光源------------------
// 環境光源
const aLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(aLight);
// 半球光源
const hLight = new THREE.HemisphereLight(0xffffff, 0.5);
scene.add(hLight);
// 物体------------------
//地面
const ground = new THREE.LineSegments(
new THREE.WireframeGeometry(
new THREE.PlaneGeometry(4000, 4000, 75, 75),
),
new THREE.LineBasicMaterial({color: 0x845080, transparent:true, opacity:0.25}),
);
ground.rotation.x = Math.PI / -2;
scene.add( ground );
//正20面体
const icosa = new THREE.Mesh(
new THREE.IcosahedronGeometry( 125 ),
new THREE.MeshPhongMaterial( { color: 0x845080 } )
);
scene.add( icosa );
// ポインターの位置、座標------------------
let pointerX = 0;
let pointerY = 0;
let x = 300;
let y = 300;
let z = 300;
//最小値、最大値
const minmax = (num) => {
return Math.min( 0.5, Math.max(-0.5,num)); //-0.5以上0.5以下
}
//座標の計算
const coordinate = () => {
//表示場所の位置
const wrapReact = wrap.getBoundingClientRect();
//ポインターが表示場所のどの位置にあるか。中心を(0,0)とする為に0.5引く
let coordinateX = (pointerX - wrapReact.left) / wrapReact.width - 0.5;
let coordinateY = (pointerY - wrapReact.top) / wrapReact.height - 0.5;
//最小値、最大値の確認(touchイベント用)
coordinateX = minmax(coordinateX);
coordinateY = minmax(coordinateY);
// 角度をラジアンに変換(座標 x 何度回転させるか x Math.PI / 180)
const phi = coordinateX * 360 * Math.PI / 180;
const theta = coordinateY * 180 * Math.PI / 180;
// カメラの座標(x500して調整)
x = 500 * Math.cos(phi) * Math.cos(theta) * -1;
y = 500 * Math.sin(theta);
z = 500 * Math.sin(phi) * Math.cos(theta);
}
//マウスの位置
wrap.addEventListener("mousemove", e => {
pointerX = e.clientX;
pointerY = e.clientY;
coordinate();
});
//リサイズ------------------
const onWindowResize = () => {
// サイズ
wrapWidth = wrap.clientWidth;
wrapHeight = wrap.clientHeight;
// レンダラー
renderer.setSize(wrapWidth, wrapHeight);
}
window.addEventListener( 'resize', onWindowResize );
//レンダリング------------------
const rendering = () => {
camera.position.set(x, y, z); // カメラの座標を設定
camera.lookAt(new THREE.Vector3(0, 0, 0));
renderer.render(scene, camera); //レンダリング
}
//初回
rendering();
//表示場所にマウスが入ったら
let tick;
wrap.addEventListener('mouseenter', e => {
tick = () => {
rendering();
requestAnimationFrame( tick );
}
requestAnimationFrame( tick );
});
//表示場所からマウスが出たら
wrap.addEventListener('mouseleave', e => {
tick = () => {
cancelAnimationFrame( tick )
}
});
//スマホ対応(touchイベント)------------------
//触れている位置
wrap.addEventListener('touchmove', e => {
pointerX = e.touches[0].clientX;
pointerY = e.touches[0].clientY;
coordinate();
});
//スクロールの制御
const handleTouchMove = (event) => {
event.preventDefault();
}
//表示場所に指が触れたら
wrap.addEventListener('touchstart', e => {
document.addEventListener('touchmove', handleTouchMove, { passive: false }); //スクロールの禁止
tick = () => {
rendering();
requestAnimationFrame( tick );
}
requestAnimationFrame( tick );
});
//指が離れたら
wrap.addEventListener('touchend', e => {
document.removeEventListener('touchmove', handleTouchMove, { passive: false }); //スクロールの許可
tick = () => {
cancelAnimationFrame( tick )
}
});
参考
mouseenterとmouseoverの違いなどDOMイベントの発生状況を可視化して調べてみたよ
WebGL開発に役立つ重要な三角関数の数式・概念まとめ (Three.js編)
2019年、JavaScriptでのスクロール一時禁止はこれだ!(スマートフォン)
関連記事
この記事は役に立ちましたか?
送信中