
ブログをNext.js14(App Router)でリファクタリングしました
インクや液体、滴のような動きをするマウスストーカーの作り方を紹介します。
完成形のコードのコピペするだけでも使えますが、コードの意味を理解することによって自分好みのマウスストーカーにカスタマイズできるようになるので表現の幅が広がるはずです。
基礎的な仕組みの説明をした後、カスタマイズした例も記載します。
今回紹介するマウスストーカーのアルゴリズムは、CodePenに公開されているRicardo Mendieta様(@mendieta)の「ink Cursor」のアルゴリズムを借用させていただいております。CodePenのソースコードはMITライセンスになります。そのため、初めに掲示するソースコード全体の上部にMITライセンスを掲載しております。
目次
「液体」や「インク」と文字にしてもいまいち伝わりにくいと思うので、まずは完成形をご覧ください。
繰り返しになりますが、CodePenに公開されているRicardo Mendieta様(@mendieta)の「ink Cursor」のアルゴリズムの一部分の解説が主となります。次節からプログラムの断片を掲載しますが、ここで掲載したソースコードの一部を切り取ったものの再表示となるため、MITライセンスの掲載は割愛しています。
デモを動かしてみると気づくと思いますが、このマウスストーカー は2種類の状態から成ります。
2つの状態
JavaScriptでマウスの状態を検知してこの2つの状態を切り替えています。
1.カーソルが止まっている状態
2.カーソルが動いている状態
液体の動きは大きく分けて2つステップで構成されています。
2つのステップ
1については、SVGフィルタを外すとその挙動が分かります。
1の円をぼかした後、コントラストに対するフィルタをかけることによって、液体のように見せているわけです。
では、次節以降でコードの解説をします。
円はDotクラスで管理し、大きい円から小さい円を順に生成して配列に格納します。
const width = 30 //球の大きさ
const amount = 26 //生成する球の数
let dots: Dot[] = [] //生成した球を格納する配列
//円のクラス
class Dot {
/* 後述 */
}
//円の生成と格納
const buildDots = () => {
for (let i = 0; i < amount; i++) {
//iが大きくなるにつれ小さい円になる(後述)
let dot = new Dot(i)
dots.push(dot)
}
}
続いてDotクラスのプロパティについて詳しくみていきます。
class Dot {
x = 0 // 現在のx座標
y = 0 // 現在のy座標
private anglespeed = 0.05 //円が動くスピード(可変)
private element = document.createElement('span')
private lockX = this.x // カーソルが止まった時のx座標
private lockY = this.y // カーソルが止まった時のy座標
private scale: number // 円の大きさ
private range: number // 円が動く範囲
private angleX = 0 // 0 ~ 2π の乱数:(後述)
private angleY = 0 // 0 ~ 2π の乱数:(後述)
constructor(
private index: number, // 円の番号(0 ~ amount)
){
// 球の大きさ(可変)
// indexに反比例 = 後に生成された円ほど小さくなる
this.scale = 1 - 0.04 * this.index
// 円が動く範囲(可変)
// scaleに反比例 = indexに反比例 = 小さい円ほど広範囲を動く
this.range = width / 2 - width / 2 * this.scale + 2
this.element.classList.add(String(this.index))
gsap.set(this.element, { scale: this.scale });
cursor?.appendChild(this.element)
}
//カーソルが止まった時に呼び出されるメソッド
public lock() {
/* 後述 */
}
//止まった円の描画
public draw() {
/* 後述 */
}
}
可変と書いてある変数を調整するとマウスストーカーの動きに影響するので、色々と試してみてください。
Dotクラスには、カーソルが静止した時の処理も含まれています。まずはlock()です。
//カーソルが止まった時に1度だけ呼び出される
public lock() {
this.lockX = this.x;//とまったときのx座標の位置
this.lockY = this.y;//とまったときのy座標の位置
this.angleX = Math.PI * 2 * Math.random();// (0 ~ 2πの乱数)。カーソルが止まった時に乱数固定
this.angleY = Math.PI * 2 * Math.random();
}
カーソルが止まるたびに呼び出され、その都度止まったマウス位置を取得し、0 ~ 2πの乱数を2つ生成します
この2つの乱数は次の描画処理で使うことになります。
実際に描画を行っているのがdraw()メソッドです。
/**
* 止まった円の描画
* indexがsineDots番目以降の円だけ動かす
* idle: boolean // マウスが止まっている時true(グローバル変数)
* sineDots: 動かす円の数(グローバル変数)
*/
public draw() {
if (!idle || this.index <= sineDots) {
gsap.set(this.element, {x: this.x, y: this.y})
} else { // カーソルが止まっている時の円の描画
this.angleX += this.anglespeed;//毎フレーム加算される
this.angleY += this.anglespeed;//同上
// this.lockX = 0
this.y = this.lockY + Math.sin(this.angleY) * this.range; //y方向の動き
this.x = this.lockX + Math.cos(this.angleX) * this.range; //x方向の動き
gsap.set(this.element, { x: this.x, y: this.y });
}
}
コードの通り、全ての円を動かしているわけではありません。sineDotesで定義した数字より大きい番号の円のみ動かしています。
カーソルが止まっている時はelseブロックが実行されます。その中で周期的な動きを実現しているのがsin()とcos()で定義されている三角関数の部分です。
※三角関数については別途記事にする予定です。
ここでの動作を簡単に説明すると、カーソル位置(lockX, lockY)から半径rangeの範囲内を早さanglespeedで周期的に動く球を描画しています。
angleXとangleYは静止するたびに発生する0 ~ 2πの乱数でした。よって、静止するたびにx座標とy座標の三角関数の初期位相が変わるため毎回異なる動きを実現しています。
// 描画更新
const positionCursor = () => {
let x = mousePosition.x
let y = mousePosition.y
dots.forEach((dot, index, dots) => {
let nextDot = dots[index + 1] || dots[0]
dot.x = x
dot.y = y
dot.draw()
//カーソルが動いている時
// 0.4は可変
if (!idle || index <= sineDots) {
const dx = (nextDot.x - dot.x) * 0.4;
const dy = (nextDot.y - dot.y) * 0.4;
x += dx;
y += dy;
}
})
}
カーソルが動いている時の処理はif文の中で行なっています。
小さい円になればなるほどdxが蓄積されて動きが遅れる単純な仕組みです。0.4などの係数をかけることで遅延する距離を調整できます。
カーソルを早く動かしすぎると円と円の間が離れすぎるため、マウスストーカー が千切れてしまいます
下に載せたカスタマイズの例で千切れないようにする簡単な例を載せてます。
以上が円のアニメーションに関する説明です。次にHTML側でSVGフィルタをかける処理について簡単に説明します。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800">
<defs>
<filter id="goo">
<!-- ぼかし -->
<feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur" />
<!-- 透過度に対してコントラストをつける -->
<feColorMatrix in="blur" mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -15" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
す。SVGフィルタやガウシアンフィルタについては別で記事にする予定です。
解説してきた各パラメータの意味を理解すれば、カスタマイズは簡単にできます。
応用例
時間経過で色が変わる、千切れないインクカーソルのデモです。
以上がインクのようなマウスストーカーの解説になります。
コードの内容を理解することによって、様々な応用例が思い浮んだかと思います。
是非参考にしてみてください。
最新の記事
カテゴリー一覧
アーカイブ