ホバーすると丸が表示されるボタン
概要
ホバーするとマウスの先端にViewと書かれた丸が表示されるボタンです。
ボタンにホバーしていることが一目でわかります。
ホバーした時にViewという文字も出てくるので、これはボタンとユーザーに認識させることができます。そのため、サンプルのように「装飾を減らしたボタン」に使うと良いかもしれません。
実装は、マウスの先端に表示する丸を描画するdivを用意し、CSSで丸のスタイルを作り、JavaScriptでホバーによりクラスのつけ外しをすることで表示/非表示を制御しています。
※スマホ環境時の処理について未記載だったため、以下追記
スマホ時はタップ開始、タップ中、タップ終了でイベントを検知し、タップ開始〜タップ終了まで丸が追従するようにしています。
タップ時は、指と重なって丸が見えにくくなるため、ずらして表示するようにしています。
ポイントとしては、isHoverCapableという定数でホバーができる環境かどうかをチェックした上で、マウス処理、タップ処理を分けることで、思わぬ動作バグのリスクを減らしています。
(実際、この条件がなかったときにタップ時に丸が消えないという事象がありました。)
コードサンプル
HTML
<button class="btn">Button</button>
<div class="cursor-circle">View</div>
CSS
/* ボタン */
.btn {
position: relative;
display: inline-block;
padding: 0.2em 0.5em;
border: none;
background: transparent;
color: #333333;
cursor: pointer;
overflow: hidden;
}
/* カーソル円 */
.cursor-circle {
position: fixed;
left: 0;
top: 0;
width: 60px;
height: 60px;
border-radius: 50%;
background: rgba(8, 37, 79, 0.8);
color: #f4f5f9;
display: flex;
align-items: center;
justify-content: center;
font-size: 1em;
font-weight: 700;
pointer-events: none; /* マウス操作に影響しない */
transform: translate(-50%, -50%);
opacity: 0;
scale: 0.8;
transition: opacity 0.3s ease, scale 0.3s ease;
z-index: 9999;
}
.cursor-circle.is-active {
opacity: 1;
scale: 1;
}
.cursor-circle { pointer-events: none; }
JavaScript
document.addEventListener("DOMContentLoaded", () => {
const btn = document.querySelector(".btn");
const circle = document.querySelector(".cursor-circle");
const isHoverCapable = window.matchMedia("(hover: hover) and (pointer: fine)").matches; // hoverができる環境かどうか
// hover あり環境(PC)
if (isHoverCapable) {
btn.addEventListener("mouseenter", () => {
circle.classList.add("is-active");
});
btn.addEventListener("mouseleave", () => {
circle.classList.remove("is-active");
});
document.addEventListener("mousemove", (e) => {
if (circle.classList.contains("is-active")) {
circle.style.left = e.clientX + "px";
circle.style.top = e.clientY + "px";
}
});
}
// スマホ(タップ中のみ表示&追従)
const setPos = (x, y) => {
const offsetX = circle.offsetWidth / 2;
const offsetY = circle.offsetHeight / 2;
circle.style.left = (x - offsetX) + "px";
circle.style.top = (y - offsetY) + "px";
};
btn.addEventListener("touchstart", (e) => {
circle.classList.add("is-active");
setPos(e.touches[0].clientX, e.touches[0].clientY);
});
btn.addEventListener("touchmove", (e) => {
if (circle.classList.contains("is-active")) {
setPos(e.touches[0].clientX, e.touches[0].clientY);
}
});
btn.addEventListener("touchend", () => {
circle.classList.remove("is-active");
});
});