Feylo

デモを見るコードを見る

スポットライトのようにボーダーがマウスに追従するカードコンポーネント

マウスホバー
スポットライトのようにボーダーがマウスに追従するカードコンポーネント

最近、よく見かけるカードコンポーネントが並べられていて、ホバーするとスポットライトのようにボーダーがマウスに追従するアニメーションを解説します。実装ではmask-imageを利用して実現しています。

    実装のポイント
  • mask-imageを使用してスポットライトのようなボーダーを作成する
  • JavaScriptでマウス位置を取得してボーダーをマウスに追従させる

実装の考え方

カードコンポーネントの中に、card__borderというクラスを作成し、position: absolute;でカードの幅・高さを合わせるようにします。このcard__borderクラスに対して、表示したいボーダーを作成します。

このままだと、ボーダーが全て表示されているので、mask-image放射状グラデーションマスクを適用し、マウスがある位置にボーダーを表示するようにします。

実装方法

それでは実際に実装していきましょう。
まずはHTMLになります。

HTML

HTML
<div class="card__wrapper">
  <div class="card">
    <div class="card__img">
      <img src="https://picsum.photos/300/200?random=1" alt="">
    </div>
    <div class="card__body">
      <h2>タイトル01</h2>
      <p>Lorem ipsum dolor sit amet consectetur.</p>
    </div>
    <div class="card__border"></div>
  </div>
  // カードの数だけ繰り返す
</div>

先述の通り、cardクラスの中にcard__borderクラスを入れます。

SCSS

CSSは以下のようになります。

SCSS
.card {
  position: relative;
  padding: 1rem;
  border: 1px solid #e4e4e7;
  border-radius: 0.5rem;
  &__wrapper {
    display: grid;
    gap: 0.5rem;
    grid-template-columns: repeat(3, 1fr);
  }
  &__border {
    --border-x: 0px;
    -border-y: 0px;
    position: absolute;
    top: -1px;
    left: -1px;
    width: calc(100% + 2px);
    height: calc(100% + 2px);
    background: transparent;
    border-radius: 0.5rem;
    pointer-events: none;
    border: 1px solid #2B80FF;
    mask-image: radial-gradient(30% 50px at var(--border-x) var(--border-y), black 45%, transparent);
    opacity: 0;
    transition-property: opacity;
    transition-duration: 0.4s;
  }
}

先述の通り、ボーダーはcard__borderとしています。このクラスに対して、position: absolute;でカードの幅・高さを合わせるようにします。

ここで下記のコードで、topとleftを-1pxにしているのは、cardクラスのボーダーが1px外側についているので、その分をずらすためです。またその影響で、widthとheightを2pxずつ増やしています。

SCSS
.card__border {
  position: absolute;
  top: -1px;
  left: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
}

mask-image

ここで、mask-imageについて説明します。
まずは、radial-gradientについてですが、これは放射状グラデーションマスクを作成するために利用しています。第1引数に30%を、第2引数に50pxを指定することで、上記画像のように楕円形のグラデーションマスクを作成され、ボーダーの横幅が多く表示されるようになります。

SCSS
.card__border {
  border: 1px solid #2B80FF;
  mask-image: radial-gradient(30% 50px at var(--border-x) var(--border-y), black 45%, transparent);
}

--border-x--border-yは、JavaScriptで取得したマウスの位置を代入するための変数になります。この値を中心に楕円形のグラデーションマスクが移動することになります。

mask-imageでこのグラデーションマスクを指定することで、黒い部分のみ表示されることで、マウスの位置がボーダー付近にある場合のみ、ボーダーが表示されるようになります。

また、初期状態でopacity: 0;で非表示にしておいて、JavaScriptでホバーした時に表示するようにします。

JavaScript

JavaScript
const cards = document.querySelectorAll('.card');

cards.forEach(card => {
  const border = card.querySelector('.card__border');

  card.addEventListener('mousemove', (e) => {
    const rect = card.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    border.style.setProperty('--border-x', `${x}px`);
    border.style.setProperty('--border-y', `${y}px`);
  })

  card.addEventListener('mouseenter', () => {
      border.style.opacity = '1';
    });

  card.addEventListener('mouseleave', () => {
    border.style.opacity = '0';
  });
});

JavaScriptでは、mousemoveイベントでカード内をマウスが移動した時に、マウスの位置を取得して、--border-x--border-ysetPropertyで代入しています。

mouseenterイベントでは、マウスがカードに入った時に、opacity: 1;で表示させ、mouseleaveイベントでは、マウスがカードから出た時に、opacity: 0;で非表示にしています。

Share