【實作紀錄】客製化鼠標軌跡效果

Ray
8 min readNov 12, 2022

--

Author

目標效果:讓鼠標滑過的軌跡有星星落下

stackblitz

概念

監聽滑鼠事件(mousemove),這樣當滑鼠有移動時,我們可以取得鼠標相關資訊(如:位置)。

要先知道每一顆星星(image)實際上就是一個帶有background-image屬性的div。假設滑鼠從p1點移動到p2點,那我就在這段距離中每隔一定時間就建立這個div,做到這邊時,看起來就會像下圖

所以我們在每個點建立一個星星後,要讓他執行動畫,這邊做的是讓他往下飄落,這就是主要的概念,下方詳細說明。

程式碼

目的是做出可以讓星星下落的動畫。分成兩個部分,第一個是星星的類別(Class),第二個是負責操控的部分(Service)。

Class

仔細觀察可以發現下落的動畫是:星星的x軸逐漸往右或往左,且星星逐漸變小,最終消失。知道邏輯後,來介紹一下變數及Function的用法

export class Star {
lifeSpan: number; // 紀錄星星是否消失
initialStyles: any; // 定義星星的CSS樣式
velocity: any; // 定義下落速度,使得每顆星星速度不同,較為自然
position: any; // 紀錄當前星星所在x,y軸之位置
element: any; // 這個星星的element資訊

constructor() {
this.lifeSpan = 900; // 表示900ms後消失
this.initialStyles = { // 基本樣式資訊(如:星星的大小、圖示來源)
"position": "absolute",
"display": "block",
"pointerEvents": "none",
"z-index": "10000000",
"will-change": "transform",
"background-image": Math.random() > 0.5 ? "url('assets/1.png')" : "url('assets/2.png')",
"width": "1.75px",
"height": "1.75px",
"background-size": "contain",
"background-repeat": "no-repeat"
};
}

init(x: any, y: any) { // 建立這個星星時需要呼叫的Function

// 主要是透過傳入當前滑鼠位置來判斷要建立在哪個座標
this.velocity = { x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2), y: 1 };
this.position = { x: x - 10, y: y - 20 };

this.element = document.createElement('div');

// 將上面宣告的CSS樣式資訊綁到這個div元素上
this.applyProperties(this.element, this.initialStyles);

this.update();

// 將這個元素綁到主畫面上
document.body.appendChild(this.element);
};

applyProperties(target: any, properties: any) {
for (var key in properties) {
target.style[key] = properties[key];
}
}

// 每呼叫一次update,就是使得星星往下一些
update() {
const nextX = this.position.x + this.velocity.x
const nextY = this.position.y + this.velocity.y

this.position.x = nextX;
this.position.y = nextY;
this.lifeSpan!--;

// 主要位移程式碼,主要是透過css的transform來調整控制
this.element.style.transform = "translate3d(" + this.position.x + "px," + this.position.y + "px,0) scale(" + (this.lifeSpan! / 120) + ")";
}

// 當這個星星已經存活超過我們定義的lifeSpan時間,則移除它,讓它消失
die() {
this.element.parentNode?.removeChild(this.element);
}
}

Service

負責建立星星以及更新星星的位置,主要就是滑鼠移動時,建立一個星星並將它推進存放星星的Array中。當這顆星星的存活時間已經超過,則將它從Array中移出,因此存放於這個Array中的星星都是畫面上看的到的。

import { Injectable } from '@angular/core';
import { tap, throttleTime } from 'rxjs/operators';
import { fromEvent, Subscription } from 'rxjs';
import { Star } from './mouse-effect.class';

@Injectable({
providedIn: 'root'
})
export class MouseEffectService {
eventSub: Subscription | undefined;
stars: any[] = [];

constructor() { }

init() {
this.eventSub = fromEvent(window, 'mousemove') // 監聽滑鼠移動事件
.pipe(
throttleTime(30), // 每隔30ms觸發一次,可以改變參數,避免太頻繁
tap((e: any) => { // 觸發時取得當前滑鼠位置,並用滑鼠位置去建立對應的星星
const cursor = { x: e.clientX, y: e.clientY };
this.addStar(cursor.x, cursor.y);
})
).subscribe();

this.loop();
}

destroy() { // 停止監聽滑鼠移動事件
this.eventSub?.unsubscribe();
}

private addStar(x: number, y: number) {
var star = new Star();
star.init(x, y);
this.stars.push(star);
}

private updateStars() {
if (!this.stars.length) { return; };

// 更新星星的位置
for (var i = 0; i < this.stars.length; i++) {
this.stars[i].update();
}

// lifeSpan已經為負數,表示存活時間超過,將它移出陣列
for (var i = this.stars.length - 1; i >= 0; i--) {
if (this.stars[i].lifeSpan < 0) {
this.stars[i].die();
this.stars.splice(i, 1);
}
}
}

// loop更新星星資訊
private loop() {
window.requestAnimationFrame(() => this.loop());
this.updateStars();
}

}

--

--

No responses yet