マウスの位置に合わせて要素を動かす(HTML/CSS/JS)

前書き

マウスの位置に合わせて要素を動かす方法を掲載しています。

目次

  1. 複数の要素を、差をつけて動かす
  2. マウスを追従、変化させる
  3. three.jsのカメラを動かす
  4. 参考

複数の要素を、差をつけて動かす

下記ページを参考にしています。

遠近感を味わえるマウスインタラクションを作ってみた!

当ページでは、枠線内でマウスを動かした時に、マウスの位置に合わせて、下記を動かしています。
また、スマホ(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

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 )
  }
});

参考

遠近感を味わえるマウスインタラクションを作ってみた!

イケてるマウスカーソルを簡単に実装する

【CSS3】Transform(変形)関連のまとめ

mouseenterとmouseoverの違いなどDOMイベントの発生状況を可視化して調べてみたよ

WebGL開発に役立つ重要な三角関数の数式・概念まとめ (Three.js編)

PCとスマホの Pointer Events 挙動まとめ

2019年、JavaScriptでのスクロール一時禁止はこれだ!(スマートフォン)

関連記事

簡単な地図を自作する(HTML/CSS/JS)

パーティクルを作成する(HTML/JS)

文字を立体で表示する(HTML/CSS/JS)

360度画像を表示する(HTML/JS)

うにょうにょ動かす(HTML/CSS/JS)

円を描くように要素を動かす(HTML/CSS/JS)

先頭に戻る
ページの先頭に戻る