テキストホバー時に画像がマウスに追従して動くように表示させてみる(備忘録)

今回は、前回のCSSのみでテキストホバー時に画像を文字背景に表示させる対応の応用編です。

テキストホバー時に画像がマウスに追従して動くように表示させてみるということをやってみます。

左右に動かすとスピードに応じて慣性が働いて傾くようなことにも挑戦してみました。

それではやっていきましょう。

HTMLの記述について

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>テキストホバー時に画像がマウスに追従して慣性が働くように表示させてみる(備忘録)</title>
  <link rel="stylesheet" href="../reset.css">
  <link rel="stylesheet" href="./style.css">
</head>

<body>
  <main>
    <ul>
      <li class="menuItem">
        <a class="menuItem-link">止まっているシマエナガ
          <img class="menuItem-img" src="images/img_01.webp" alt="" />
        </a>
      </li>
      <li class="menuItem">
        <a class="menuItem-link">飛んでいるシマエナガ
          <img class="menuItem-img" src="images/img_02.webp" alt="" />
        </a>
      </li>
    </ul>
  </main>
  <script src="./style.js"></script>
</body>

</html>

こちらは前回同様シンプルな構成になっています。

前回の記事はこちら

CSSの記述について

@charset "utf-8";

/* ==========================
  初期設定
========================== */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  position: relative;
  word-wrap: break-word;
}

img {
  width: 100%;
  vertical-align: bottom;
}

/* レイアウト設定 */
body {
  font-family: sans-serif;
}

main {
  margin: 80px 0;
  padding: 0 24px;
}

ul {
  list-style: none;
}

.menuItem-link {
  font-size: 80px;
  cursor: pointer;
  position: relative;
  color: transparent;
  -webkit-text-stroke: 1px #000;
  text-stroke: 1px #000;
  transition: color 0.3s;
  font-weight: bold;
  display: block;
  width: fit-content;
}

.menuItem-link:hover {
  color: #c3512f;
  -webkit-text-stroke: 1px #c3512f;
  text-stroke: 1px #c3512f;
}

.menuItem-img {
  z-index: -1;
  position: fixed;
  width: 320px;
  max-height: 240px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s, clip-path 0.5s ease-out, left 0.6s ease-out,
    top 0.6s ease-out, transform 0.6s ease-out;
  clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
}

CSSはそこまで難しいことはしていません。今回は、clip-pathを使ってホバー時にカーテンのように画像が表示されるようにしたかったたため、取り入れています。

JavaScriptの記述について

"use strict";

// 'menuItem-link' クラスを持つすべての要素を取得してそれぞれに対して処理を行う
document.querySelectorAll(".menuItem-link").forEach((link) => {
  // 各リンクに関連付けられた画像を取得
  const img = link.querySelector(".menuItem-img");
  // マウスの最後のX座標を保持する変数
  let lastX = 0;

  // マウスがリンクエリアに入ったときのイベントリスナー
  link.addEventListener("mouseenter", function (e) {
    lastX = e.clientX; // マウスが入った点のX座標を記録
    img.style.opacity = "1"; // 画像を表示
    img.style.clipPath = "polygon(0 0, 100% 0, 100% 100%, 0 100%)"; // クリップパスを設定して画像が左から右に現れるアニメーションを開始
  });

  // マウスがリンク上を移動するときのイベントリスナー
  link.addEventListener("mousemove", function (e) {
    requestAnimationFrame(function () {
      // requestAnimationFrameを使用して更新をブラウザの再描画に同期させる
      const rotation = (e.clientX - lastX) / 4; // 前回のX座標からの変化に基づいて回転角度を計算
      lastX = e.clientX;
      img.style.left = e.clientX - img.width / 2 + "px"; // 画像の中心がマウスカーソルになるように左位置を設定
      img.style.top = e.clientY - img.height / 2 + "px"; // 画像の中心がマウスカーソルになるように上位置を設定
      img.style.transform = `rotate(${rotation}deg)`; // 計算した角度で画像を回転
    });
  });

  // マウスがリンクエリアから出たときのイベントリスナー
  link.addEventListener("mouseleave", function () {
    img.style.opacity = "0"; // 画像を非表示に設定
    img.style.clipPath = "polygon(0 0, 0 0, 0 100%, 0 100%)"; // クリップパスをリセット
    img.style.transform = ""; // 回転をリセット
  });
});

内容はコメントアウトに書いてあるとおりになります。

マウスがリンクエリアに入ったとき、移動しているとき、リンクエリアから出たときでそれぞれ処理内容を記述しています。

今回は、2回目以降にリンクエリアにマウスが入ったときに、画像が前回のリンクエリアに入った場所から移動するようになっています。

これを解消するための処理を色々試していましたが、自分には難しかったため、今回は諦めています。

それでも、結構いい感じに対応できていると自分では思います。皆さんの参考になりますと幸いです。