下雨光标样式测试

This commit is contained in:
2025-09-15 18:19:40 +08:00
parent c0aa41e28a
commit 552bd91e03
13 changed files with 1424 additions and 1 deletions

1
cursor-rain Submodule

Submodule cursor-rain added at 583adeb0f7

View File

@@ -0,0 +1,28 @@
import type { RainDropOptions, CursorRainEffect as ICursorRainEffect } from './types';
export declare class CursorRainEffect implements ICursorRainEffect {
private options;
private container;
private rainContainer;
private drops;
private isInitialized;
private mouseMoveHandler;
private timeoutId;
private animationPool;
constructor(options?: RainDropOptions);
init(): void;
destroy(): void;
enable(): void;
disable(): void;
updateOptions(newOptions: Partial<RainDropOptions>): void;
private createRainContainer;
private preCreateDrops;
private createDropElement;
private setupEventListeners;
private removeEventListeners;
private createRainAtPosition;
private getAvailableDrop;
private returnDropToPool;
private randomBetween;
private throttle;
private cleanup;
}

25
docs/.vitepress/cursor-rain/index.d.ts vendored Normal file
View File

@@ -0,0 +1,25 @@
import { CursorRainEffect } from './CursorRainEffect';
import type { RainDropOptions, CursorRainEffect as ICursorRainEffect } from './types';
/**
* Create a new cursor rain effect instance
*/
export declare function createCursorRainEffect(options?: RainDropOptions): ICursorRainEffect;
/**
* Initialize cursor rain effect with default options
* This is a convenience function for quick setup
*/
export declare function initCursorRain(options?: RainDropOptions): ICursorRainEffect;
/**
* VitePress compatible initialization
* This function ensures the effect works correctly in VitePress environment
*/
export declare function initCursorRainForVitePress(options?: RainDropOptions): ICursorRainEffect;
export { CursorRainEffect } from './CursorRainEffect';
export type { RainDropOptions, RainDrop, CursorRainEffect as ICursorRainEffect } from './types';
declare const _default: {
createCursorRainEffect: typeof createCursorRainEffect;
initCursorRain: typeof initCursorRain;
initCursorRainForVitePress: typeof initCursorRainForVitePress;
CursorRainEffect: typeof CursorRainEffect;
};
export default _default;

View File

@@ -0,0 +1,307 @@
import { gsap } from 'gsap';
class CursorRainEffect {
constructor(options = {}) {
this.rainContainer = null;
this.drops = [];
this.isInitialized = false;
this.mouseMoveHandler = null;
this.timeoutId = null;
this.animationPool = [];
this.options = {
maxDrops: options.maxDrops ?? 50,
dropSize: options.dropSize ?? [2, 8],
color: options.color ?? 'rgba(173, 216, 230, 0.8)',
duration: options.duration ?? [0.8, 1.5],
delay: options.delay ?? 100,
enabled: options.enabled ?? true,
zIndex: options.zIndex ?? 9999,
container: options.container ?? document.body
};
this.container = this.options.container;
}
init() {
if (this.isInitialized) {
return;
}
this.createRainContainer();
this.setupEventListeners();
this.preCreateDrops();
this.isInitialized = true;
}
destroy() {
if (!this.isInitialized) {
return;
}
this.cleanup();
this.isInitialized = false;
}
enable() {
this.options.enabled = true;
if (this.isInitialized) {
this.setupEventListeners();
}
}
disable() {
this.options.enabled = false;
this.removeEventListeners();
}
updateOptions(newOptions) {
this.options = { ...this.options, ...newOptions };
if (newOptions.container && newOptions.container !== this.container) {
this.container = newOptions.container;
if (this.isInitialized) {
this.destroy();
this.init();
}
}
}
createRainContainer() {
this.rainContainer = document.createElement('div');
this.rainContainer.className = 'cursor-rain-container';
this.rainContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: ${this.options.zIndex};
overflow: hidden;
`;
this.container.appendChild(this.rainContainer);
}
preCreateDrops() {
// Pre-create drop elements for better performance
for (let i = 0; i < this.options.maxDrops; i++) {
const drop = this.createDropElement();
this.animationPool.push(drop);
if (this.rainContainer) {
this.rainContainer.appendChild(drop);
}
}
}
createDropElement() {
const drop = document.createElement('div');
drop.className = 'rain-drop';
drop.style.cssText = `
position: absolute;
background: ${this.options.color};
border-radius: 50% 50% 50% 50% / 90% 90% 10% 10%;
pointer-events: none;
opacity: 0;
transform-origin: center bottom;
`;
return drop;
}
setupEventListeners() {
if (!this.options.enabled) {
return;
}
this.removeEventListeners();
this.mouseMoveHandler = this.throttle((e) => {
this.createRainAtPosition(e.clientX, e.clientY);
}, 16); // ~60fps throttling
document.addEventListener('mousemove', this.mouseMoveHandler, { passive: true });
}
removeEventListeners() {
if (this.mouseMoveHandler) {
document.removeEventListener('mousemove', this.mouseMoveHandler);
this.mouseMoveHandler = null;
}
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
createRainAtPosition(x, y) {
if (!this.rainContainer || this.drops.length >= this.options.maxDrops) {
return;
}
// Get available drop from pool
const dropElement = this.getAvailableDrop();
if (!dropElement) {
return;
}
const size = this.randomBetween(this.options.dropSize[0], this.options.dropSize[1]);
const duration = this.randomBetween(this.options.duration[0], this.options.duration[1]);
// Add some randomness to position
const offsetX = this.randomBetween(-20, 20);
const offsetY = this.randomBetween(-10, 10);
const finalX = x + offsetX;
const finalY = y + offsetY;
const drop = {
element: dropElement,
x: finalX,
y: finalY,
size,
isAnimating: true
};
this.drops.push(drop);
// Set initial position and size
gsap.set(dropElement, {
x: finalX - size / 4, // 调整X偏移因为宽度变小了
y: finalY - size / 2,
width: size * 0.3, // 宽度减小到原来的30%,让雨滴更细
height: size * 4, // 高度增加到4倍让雨滴更长
opacity: 0,
scaleY: 0.1,
rotation: this.randomBetween(-15, 15)
});
// Animate the raindrop - continuous fall with gradual rotation change
const initialRotation = this.randomBetween(-15, 15); // 初始随机角度
const fallDistance = window.innerHeight - finalY + 100;
const tl = gsap.timeline({
onComplete: () => {
this.returnDropToPool(drop);
}
});
// 快速出现
tl.to(dropElement, {
opacity: 1,
scaleY: 1,
duration: 0.1,
ease: 'power2.out'
})
// 连续下落:一次性完成整个下落过程,同时角度逐渐变垂直
.to(dropElement, {
y: `+=${fallDistance}`, // 一次性完成所有下落距离
x: `+=${initialRotation * 0.3}`, // 轻微的水平漂移
scaleY: 1.5, // 逐渐拉长
opacity: 0.3, // 逐渐变透明
rotation: 0, // 角度从初始角度平滑变为垂直
duration: duration,
ease: 'power1.in' // 重力加速效果
}, 0.05);
}
getAvailableDrop() {
return this.animationPool.find(drop => gsap.getTweensOf(drop).length === 0) || null;
}
returnDropToPool(drop) {
const index = this.drops.indexOf(drop);
if (index > -1) {
this.drops.splice(index, 1);
}
drop.isAnimating = false;
// Reset the element
gsap.set(drop.element, {
opacity: 0,
x: 0,
y: 0,
scaleY: 1,
rotation: 0
});
}
randomBetween(min, max) {
return Math.random() * (max - min) + min;
}
throttle(func, delay) {
let timeoutId = null;
let lastExecTime = 0;
return (...args) => {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func(...args);
lastExecTime = currentTime;
}
else {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = window.setTimeout(() => {
func(...args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
cleanup() {
this.removeEventListeners();
// Kill all GSAP animations
this.drops.forEach(drop => {
gsap.killTweensOf(drop.element);
});
this.drops = [];
this.animationPool = [];
if (this.rainContainer && this.rainContainer.parentNode) {
this.rainContainer.parentNode.removeChild(this.rainContainer);
this.rainContainer = null;
}
}
}
/**
* Create a new cursor rain effect instance
*/
function createCursorRainEffect(options) {
return new CursorRainEffect(options);
}
/**
* Initialize cursor rain effect with default options
* This is a convenience function for quick setup
*/
function initCursorRain(options) {
const effect = createCursorRainEffect(options);
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
effect.init();
});
}
else {
// DOM is already ready
effect.init();
}
return effect;
}
/**
* VitePress compatible initialization
* This function ensures the effect works correctly in VitePress environment
*/
function initCursorRainForVitePress(options) {
const effect = createCursorRainEffect({
container: document.body,
zIndex: 1000, // Lower z-index to avoid conflicts with VitePress UI
...options
});
// Handle VitePress page navigation
const initEffect = () => {
// Small delay to ensure VitePress has finished rendering
setTimeout(() => {
effect.init();
}, 100);
};
// Handle both initial load and client-side navigation
if (typeof window !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEffect);
}
else {
initEffect();
}
// Handle VitePress client-side navigation
window.addEventListener('popstate', () => {
effect.destroy();
initEffect();
});
// Handle programmatic navigation (if using Vue Router)
if (window.history && window.history.pushState) {
const originalPushState = window.history.pushState;
window.history.pushState = function (...args) {
originalPushState.apply(window.history, args);
effect.destroy();
initEffect();
};
}
}
return effect;
}
// Default export for convenience
var index = {
createCursorRainEffect,
initCursorRain,
initCursorRainForVitePress,
CursorRainEffect
};
export { CursorRainEffect, createCursorRainEffect, index as default, initCursorRain, initCursorRainForVitePress };

View File

@@ -0,0 +1,315 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var gsap = require('gsap');
class CursorRainEffect {
constructor(options = {}) {
this.rainContainer = null;
this.drops = [];
this.isInitialized = false;
this.mouseMoveHandler = null;
this.timeoutId = null;
this.animationPool = [];
this.options = {
maxDrops: options.maxDrops ?? 50,
dropSize: options.dropSize ?? [2, 8],
color: options.color ?? 'rgba(173, 216, 230, 0.8)',
duration: options.duration ?? [0.8, 1.5],
delay: options.delay ?? 100,
enabled: options.enabled ?? true,
zIndex: options.zIndex ?? 9999,
container: options.container ?? document.body
};
this.container = this.options.container;
}
init() {
if (this.isInitialized) {
return;
}
this.createRainContainer();
this.setupEventListeners();
this.preCreateDrops();
this.isInitialized = true;
}
destroy() {
if (!this.isInitialized) {
return;
}
this.cleanup();
this.isInitialized = false;
}
enable() {
this.options.enabled = true;
if (this.isInitialized) {
this.setupEventListeners();
}
}
disable() {
this.options.enabled = false;
this.removeEventListeners();
}
updateOptions(newOptions) {
this.options = { ...this.options, ...newOptions };
if (newOptions.container && newOptions.container !== this.container) {
this.container = newOptions.container;
if (this.isInitialized) {
this.destroy();
this.init();
}
}
}
createRainContainer() {
this.rainContainer = document.createElement('div');
this.rainContainer.className = 'cursor-rain-container';
this.rainContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: ${this.options.zIndex};
overflow: hidden;
`;
this.container.appendChild(this.rainContainer);
}
preCreateDrops() {
// Pre-create drop elements for better performance
for (let i = 0; i < this.options.maxDrops; i++) {
const drop = this.createDropElement();
this.animationPool.push(drop);
if (this.rainContainer) {
this.rainContainer.appendChild(drop);
}
}
}
createDropElement() {
const drop = document.createElement('div');
drop.className = 'rain-drop';
drop.style.cssText = `
position: absolute;
background: ${this.options.color};
border-radius: 50% 50% 50% 50% / 90% 90% 10% 10%;
pointer-events: none;
opacity: 0;
transform-origin: center bottom;
`;
return drop;
}
setupEventListeners() {
if (!this.options.enabled) {
return;
}
this.removeEventListeners();
this.mouseMoveHandler = this.throttle((e) => {
this.createRainAtPosition(e.clientX, e.clientY);
}, 16); // ~60fps throttling
document.addEventListener('mousemove', this.mouseMoveHandler, { passive: true });
}
removeEventListeners() {
if (this.mouseMoveHandler) {
document.removeEventListener('mousemove', this.mouseMoveHandler);
this.mouseMoveHandler = null;
}
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
createRainAtPosition(x, y) {
if (!this.rainContainer || this.drops.length >= this.options.maxDrops) {
return;
}
// Get available drop from pool
const dropElement = this.getAvailableDrop();
if (!dropElement) {
return;
}
const size = this.randomBetween(this.options.dropSize[0], this.options.dropSize[1]);
const duration = this.randomBetween(this.options.duration[0], this.options.duration[1]);
// Add some randomness to position
const offsetX = this.randomBetween(-20, 20);
const offsetY = this.randomBetween(-10, 10);
const finalX = x + offsetX;
const finalY = y + offsetY;
const drop = {
element: dropElement,
x: finalX,
y: finalY,
size,
isAnimating: true
};
this.drops.push(drop);
// Set initial position and size
gsap.gsap.set(dropElement, {
x: finalX - size / 4, // 调整X偏移因为宽度变小了
y: finalY - size / 2,
width: size * 0.3, // 宽度减小到原来的30%,让雨滴更细
height: size * 4, // 高度增加到4倍让雨滴更长
opacity: 0,
scaleY: 0.1,
rotation: this.randomBetween(-15, 15)
});
// Animate the raindrop - continuous fall with gradual rotation change
const initialRotation = this.randomBetween(-15, 15); // 初始随机角度
const fallDistance = window.innerHeight - finalY + 100;
const tl = gsap.gsap.timeline({
onComplete: () => {
this.returnDropToPool(drop);
}
});
// 快速出现
tl.to(dropElement, {
opacity: 1,
scaleY: 1,
duration: 0.1,
ease: 'power2.out'
})
// 连续下落:一次性完成整个下落过程,同时角度逐渐变垂直
.to(dropElement, {
y: `+=${fallDistance}`, // 一次性完成所有下落距离
x: `+=${initialRotation * 0.3}`, // 轻微的水平漂移
scaleY: 1.5, // 逐渐拉长
opacity: 0.3, // 逐渐变透明
rotation: 0, // 角度从初始角度平滑变为垂直
duration: duration,
ease: 'power1.in' // 重力加速效果
}, 0.05);
}
getAvailableDrop() {
return this.animationPool.find(drop => gsap.gsap.getTweensOf(drop).length === 0) || null;
}
returnDropToPool(drop) {
const index = this.drops.indexOf(drop);
if (index > -1) {
this.drops.splice(index, 1);
}
drop.isAnimating = false;
// Reset the element
gsap.gsap.set(drop.element, {
opacity: 0,
x: 0,
y: 0,
scaleY: 1,
rotation: 0
});
}
randomBetween(min, max) {
return Math.random() * (max - min) + min;
}
throttle(func, delay) {
let timeoutId = null;
let lastExecTime = 0;
return (...args) => {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func(...args);
lastExecTime = currentTime;
}
else {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = window.setTimeout(() => {
func(...args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
cleanup() {
this.removeEventListeners();
// Kill all GSAP animations
this.drops.forEach(drop => {
gsap.gsap.killTweensOf(drop.element);
});
this.drops = [];
this.animationPool = [];
if (this.rainContainer && this.rainContainer.parentNode) {
this.rainContainer.parentNode.removeChild(this.rainContainer);
this.rainContainer = null;
}
}
}
/**
* Create a new cursor rain effect instance
*/
function createCursorRainEffect(options) {
return new CursorRainEffect(options);
}
/**
* Initialize cursor rain effect with default options
* This is a convenience function for quick setup
*/
function initCursorRain(options) {
const effect = createCursorRainEffect(options);
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
effect.init();
});
}
else {
// DOM is already ready
effect.init();
}
return effect;
}
/**
* VitePress compatible initialization
* This function ensures the effect works correctly in VitePress environment
*/
function initCursorRainForVitePress(options) {
const effect = createCursorRainEffect({
container: document.body,
zIndex: 1000, // Lower z-index to avoid conflicts with VitePress UI
...options
});
// Handle VitePress page navigation
const initEffect = () => {
// Small delay to ensure VitePress has finished rendering
setTimeout(() => {
effect.init();
}, 100);
};
// Handle both initial load and client-side navigation
if (typeof window !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEffect);
}
else {
initEffect();
}
// Handle VitePress client-side navigation
window.addEventListener('popstate', () => {
effect.destroy();
initEffect();
});
// Handle programmatic navigation (if using Vue Router)
if (window.history && window.history.pushState) {
const originalPushState = window.history.pushState;
window.history.pushState = function (...args) {
originalPushState.apply(window.history, args);
effect.destroy();
initEffect();
};
}
}
return effect;
}
// Default export for convenience
var index = {
createCursorRainEffect,
initCursorRain,
initCursorRainForVitePress,
CursorRainEffect
};
exports.CursorRainEffect = CursorRainEffect;
exports.createCursorRainEffect = createCursorRainEffect;
exports.default = index;
exports.initCursorRain = initCursorRain;
exports.initCursorRainForVitePress = initCursorRainForVitePress;

71
docs/.vitepress/cursor-rain/types.d.ts vendored Normal file
View File

@@ -0,0 +1,71 @@
export interface RainDropOptions {
/**
* Maximum number of raindrops to show at once
* @default 50
*/
maxDrops?: number;
/**
* Size range for raindrops [min, max]
* @default [2, 8]
*/
dropSize?: [number, number];
/**
* Color of the raindrops
* @default 'rgba(173, 216, 230, 0.8)'
*/
color?: string;
/**
* Animation duration range in seconds [min, max]
* @default [0.8, 1.5]
*/
duration?: [number, number];
/**
* Delay between cursor move and rain start in milliseconds
* @default 100
*/
delay?: number;
/**
* Whether to enable the effect
* @default true
*/
enabled?: boolean;
/**
* Z-index for the rain container
* @default 9999
*/
zIndex?: number;
/**
* Container element to attach the rain effect
* @default document.body
*/
container?: HTMLElement;
}
export interface RainDrop {
element: HTMLElement;
x: number;
y: number;
size: number;
isAnimating: boolean;
}
export interface CursorRainEffect {
/**
* Initialize the rain effect
*/
init(): void;
/**
* Destroy the rain effect and clean up
*/
destroy(): void;
/**
* Enable the rain effect
*/
enable(): void;
/**
* Disable the rain effect
*/
disable(): void;
/**
* Update options
*/
updateOptions(options: Partial<RainDropOptions>): void;
}

View File

@@ -0,0 +1,125 @@
// 光标雨点效果配置文件
// 你可以在这里定义不同的配置预设
import type { RainDropOptions } from '../cursor-rain/types'
// 预设配置
export const rainPresets = {
// 默认配置
default: {
maxDrops: 25,
color: 'rgba(173, 216, 230, 0.6)',
duration: [1.0, 2.0],
dropSize: [3, 8],
delay: 100,
zIndex: 1000
} as RainDropOptions,
// 轻量模式(适合低性能设备)
light: {
maxDrops: 15,
color: 'rgba(173, 216, 230, 0.5)',
duration: [1.2, 2.5],
dropSize: [2, 6],
delay: 150,
zIndex: 1000
} as RainDropOptions,
// 华丽模式
fancy: {
maxDrops: 40,
color: 'rgba(173, 216, 230, 0.8)',
duration: [0.8, 1.5],
dropSize: [4, 12],
delay: 50,
zIndex: 1000
} as RainDropOptions,
// 粉色主题
pink: {
maxDrops: 30,
color: 'rgba(255, 182, 193, 0.7)',
duration: [1.0, 2.0],
dropSize: [3, 9],
delay: 80,
zIndex: 1000
} as RainDropOptions,
// 绿色主题
green: {
maxDrops: 25,
color: 'rgba(144, 238, 144, 0.7)',
duration: [1.0, 2.0],
dropSize: [3, 8],
delay: 100,
zIndex: 1000
} as RainDropOptions,
// 金色主题
gold: {
maxDrops: 20,
color: 'rgba(255, 215, 0, 0.6)',
duration: [1.5, 2.5],
dropSize: [2, 6],
delay: 120,
zIndex: 1000
} as RainDropOptions,
// 移动端优化配置
mobile: {
maxDrops: 10,
color: 'rgba(173, 216, 230, 0.5)',
duration: [1.5, 3.0],
dropSize: [2, 5],
delay: 200,
zIndex: 1000
} as RainDropOptions
}
// 根据设备类型自动选择配置
export function getOptimalConfig(): RainDropOptions {
if (typeof window === 'undefined') return rainPresets.default
// 检测移动设备
const isMobile = window.innerWidth < 768 || /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
// 检测低性能设备(简单的启发式检测)
const isLowPerformance = navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4
if (isMobile) {
return rainPresets.mobile
} else if (isLowPerformance) {
return rainPresets.light
} else {
return rainPresets.default
}
}
// 主题配色方案
export const colorThemes = {
blue: 'rgba(100, 149, 237, 0.7)',
lightBlue: 'rgba(173, 216, 230, 0.6)',
pink: 'rgba(255, 182, 193, 0.7)',
green: 'rgba(144, 238, 144, 0.7)',
gold: 'rgba(255, 215, 0, 0.6)',
purple: 'rgba(147, 112, 219, 0.7)',
orange: 'rgba(255, 165, 0, 0.7)',
red: 'rgba(255, 99, 99, 0.7)'
}
// 动态切换主题的函数
export function switchRainTheme(effect: any, themeName: keyof typeof colorThemes) {
const color = colorThemes[themeName]
if (color && effect) {
effect.updateOptions({ color })
// 同时更新CSS类名以应用对应的样式
const container = document.querySelector('.cursor-rain-container')
if (container) {
// 移除所有主题类
container.classList.remove('rain-theme-blue', 'rain-theme-pink', 'rain-theme-green', 'rain-theme-gold', 'rain-theme-rainbow')
// 添加新主题类
container.classList.add(`rain-theme-${themeName}`)
}
}
}

View File

@@ -0,0 +1,108 @@
/* 光标雨点效果自定义样式 */
/* 基础雨点样式 */
.cursor-rain-container {
pointer-events: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.cursor-rain-container .rain-drop {
position: absolute;
pointer-events: none;
/* 细长雨滴形状 - 更像雨丝的细长形状 - 使用!important覆盖内联样式 */
border-radius: 50% 50% 50% 50% / 90% 90% 10% 10% !important;
/* 添加一些视觉效果 */
box-shadow: 0 0 6px rgba(173, 216, 230, 0.4);
filter: blur(0.5px);
/* 渐变效果 - 垂直方向的渐变更符合拉长的雨滴 */
background: linear-gradient(to bottom,
rgba(173, 216, 230, 0.9) 0%,
rgba(173, 216, 230, 0.7) 50%,
rgba(173, 216, 230, 0.3) 100%) !important;
}
/* 深色主题适配 */
@media (prefers-color-scheme: dark) {
.cursor-rain-container .rain-drop {
background: linear-gradient(to bottom,
rgba(173, 216, 230, 0.7) 0%,
rgba(173, 216, 230, 0.5) 50%,
rgba(173, 216, 230, 0.2) 100%);
box-shadow: 0 0 8px rgba(173, 216, 230, 0.3);
}
}
/* 移动端优化 */
@media (max-width: 768px) {
.cursor-rain-container .rain-drop {
/* 移动端减少模糊效果以提高性能 */
filter: none;
box-shadow: 0 0 4px rgba(173, 216, 230, 0.3);
}
}
/* 自定义颜色主题类 */
.rain-theme-blue .rain-drop {
background: linear-gradient(to bottom,
rgba(100, 149, 237, 0.9) 0%,
rgba(100, 149, 237, 0.7) 50%,
rgba(100, 149, 237, 0.3) 100%);
box-shadow: 0 0 6px rgba(100, 149, 237, 0.4);
}
.rain-theme-pink .rain-drop {
background: linear-gradient(to bottom,
rgba(255, 182, 193, 0.9) 0%,
rgba(255, 182, 193, 0.7) 50%,
rgba(255, 182, 193, 0.3) 100%);
box-shadow: 0 0 6px rgba(255, 182, 193, 0.4);
}
.rain-theme-green .rain-drop {
background: linear-gradient(to bottom,
rgba(144, 238, 144, 0.9) 0%,
rgba(144, 238, 144, 0.7) 50%,
rgba(144, 238, 144, 0.3) 100%);
box-shadow: 0 0 6px rgba(144, 238, 144, 0.4);
}
.rain-theme-gold .rain-drop {
background: linear-gradient(to bottom,
rgba(255, 215, 0, 0.9) 0%,
rgba(255, 215, 0, 0.7) 50%,
rgba(255, 215, 0, 0.3) 100%);
box-shadow: 0 0 6px rgba(255, 215, 0, 0.4);
}
/* 特殊效果:彩虹雨点 */
.rain-theme-rainbow .rain-drop:nth-child(6n+1) {
background: linear-gradient(to bottom, rgba(255, 99, 99, 0.9) 0%, rgba(255, 99, 99, 0.7) 50%, rgba(255, 99, 99, 0.3) 100%);
}
.rain-theme-rainbow .rain-drop:nth-child(6n+2) {
background: linear-gradient(to bottom, rgba(255, 159, 64, 0.9) 0%, rgba(255, 159, 64, 0.7) 50%, rgba(255, 159, 64, 0.3) 100%);
}
.rain-theme-rainbow .rain-drop:nth-child(6n+3) {
background: linear-gradient(to bottom, rgba(255, 205, 86, 0.9) 0%, rgba(255, 205, 86, 0.7) 50%, rgba(255, 205, 86, 0.3) 100%);
}
.rain-theme-rainbow .rain-drop:nth-child(6n+4) {
background: linear-gradient(to bottom, rgba(75, 192, 192, 0.9) 0%, rgba(75, 192, 192, 0.7) 50%, rgba(75, 192, 192, 0.3) 100%);
}
.rain-theme-rainbow .rain-drop:nth-child(6n+5) {
background: linear-gradient(to bottom, rgba(54, 162, 235, 0.9) 0%, rgba(54, 162, 235, 0.7) 50%, rgba(54, 162, 235, 0.3) 100%);
}
.rain-theme-rainbow .rain-drop:nth-child(6n+6) {
background: linear-gradient(to bottom, rgba(153, 102, 255, 0.9) 0%, rgba(153, 102, 255, 0.7) 50%, rgba(153, 102, 255, 0.3) 100%);
}

View File

@@ -1,10 +1,46 @@
import BlogTheme from '@sugarat/theme'
import { initCursorRainForVitePress } from '../cursor-rain/index.esm.js'
import { gsap } from 'gsap'
import { getOptimalConfig, rainPresets } from './cursor-rain-config'
// 自定义样式重载
import './style.scss'
import './cursor-rain-styles.css'
// 自定义主题色
// import './user-theme.css'
export default BlogTheme
export default {
...BlogTheme,
enhanceApp({ app, router, siteData }) {
// 调用原主题的 enhanceApp
if (BlogTheme.enhanceApp) {
BlogTheme.enhanceApp({ app, router, siteData })
}
// 初始化光标雨点效果
if (typeof window !== 'undefined') {
// 确保 GSAP 可用
if (typeof gsap !== 'undefined') {
// 使用智能配置,根据设备自动选择最佳设置
const config = getOptimalConfig()
// 你可以在这里选择不同的预设:
// const config = rainPresets.default // 默认
// const config = rainPresets.light // 轻量模式
// const config = rainPresets.fancy // 华丽模式
// const config = rainPresets.pink // 粉色主题
// const config = rainPresets.green // 绿色主题
// const config = rainPresets.gold // 金色主题
const rainEffect = initCursorRainForVitePress(config)
// 将雨点效果实例挂载到全局,方便调试和动态控制
if (typeof window !== 'undefined') {
(window as any).rainEffect = rainEffect
}
}
}
}
}

View File

@@ -0,0 +1,195 @@
# 光标雨点效果使用指南
## 简介
这个博客已经集成了美丽的光标雨点效果!当你移动鼠标时,会在光标位置产生逼真的雨滴落下动画。
## 基础配置
### 1. 预设主题
`docs/.vitepress/theme/index.ts` 文件中,你可以选择不同的预设主题:
```typescript
// 默认蓝色主题(智能配置)
const config = getOptimalConfig()
// 或者手动选择预设:
const config = rainPresets.default // 默认
const config = rainPresets.light // 轻量模式(适合低性能设备)
const config = rainPresets.fancy // 华丽模式(更多雨滴)
const config = rainPresets.pink // 粉色主题
const config = rainPresets.green // 绿色主题
const config = rainPresets.gold // 金色主题
const config = rainPresets.mobile // 移动端优化
```
### 2. 自定义配置
你也可以完全自定义配置参数:
```typescript
const customConfig = {
maxDrops: 30, // 最大雨滴数量
color: 'rgba(255, 182, 193, 0.7)', // 雨滴颜色
duration: [1.0, 2.0], // 动画持续时间范围(秒)
dropSize: [3, 8], // 雨滴大小范围
delay: 100, // 鼠标移动到雨滴开始的延迟(毫秒)
zIndex: 1000, // 层级
enabled: true // 是否启用
}
const rainEffect = initCursorRainForVitePress(customConfig)
```
## 高级自定义
### 1. CSS 样式自定义
`docs/.vitepress/theme/cursor-rain-styles.css` 中,你可以自定义雨滴的视觉效果:
```css
/* 自定义雨滴样式 */
.cursor-rain-container .rain-drop {
/* 添加发光效果 */
box-shadow: 0 0 10px rgba(173, 216, 230, 0.6);
/* 添加模糊效果 */
filter: blur(1px);
/* 渐变背景 */
background: radial-gradient(circle,
rgba(173, 216, 230, 0.8) 0%,
rgba(173, 216, 230, 0.3) 70%,
transparent 100%);
}
```
### 2. 主题色彩
已经预设了多种主题色彩,包括:
- `blue` - 蓝色
- `lightBlue` - 浅蓝色
- `pink` - 粉色
- `green` - 绿色
- `gold` - 金色
- `purple` - 紫色
- `orange` - 橙色
- `red` - 红色
- `rainbow` - 彩虹色(特殊效果)
### 3. 动态控制
雨点效果实例已挂载到全局 `window.rainEffect`,你可以在浏览器控制台中动态控制:
```javascript
// 暂停效果
window.rainEffect.disable()
// 恢复效果
window.rainEffect.enable()
// 更新配置
window.rainEffect.updateOptions({
maxDrops: 50,
color: 'rgba(255, 99, 99, 0.8)'
})
// 切换主题(需要先导入 switchRainTheme 函数)
switchRainTheme(window.rainEffect, 'pink')
```
## 性能优化
### 1. 自动优化
系统会根据设备性能自动选择最佳配置:
- **移动设备**:自动使用移动端优化配置(更少雨滴,更长动画时间)
- **低性能设备**:自动使用轻量模式
- **高性能设备**:使用默认或华丽模式
### 2. 手动优化
如果遇到性能问题,可以手动调整:
```typescript
// 性能友好的配置
const performanceConfig = {
maxDrops: 10, // 减少雨滴数量
duration: [2.0, 3.0], // 增加动画时间
delay: 200, // 增加延迟
dropSize: [2, 4] // 减小雨滴尺寸
}
```
### 3. 移动端适配
CSS 中已包含移动端优化:
```css
@media (max-width: 768px) {
.cursor-rain-container .rain-drop {
/* 移动端减少视觉效果以提高性能 */
filter: none;
box-shadow: 0 0 4px rgba(173, 216, 230, 0.3);
}
}
```
## 常见问题
### Q: 如何完全禁用雨点效果?
A: 在配置中设置 `enabled: false`
```typescript
const config = {
...getOptimalConfig(),
enabled: false
}
```
### Q: 雨点效果影响性能怎么办?
A: 使用轻量模式:
```typescript
const config = rainPresets.light
```
或者手动减少雨滴数量:
```typescript
const config = {
...getOptimalConfig(),
maxDrops: 5
}
```
### Q: 如何在特定页面禁用效果?
A: 可以在页面的 frontmatter 中添加标识,然后在主题中根据路由判断是否启用。
### Q: 深色模式下雨点不明显怎么办?
A: CSS 中已包含深色模式适配,或者手动调整颜色:
```typescript
const config = {
...getOptimalConfig(),
color: 'rgba(173, 216, 230, 0.9)' // 增加透明度
}
```
## 开发调试
在开发过程中,你可以:
1. 打开浏览器控制台
2. 使用 `window.rainEffect` 实时调试
3. 修改配置后刷新页面查看效果
4. 使用 `window.rainEffect.updateOptions()` 实时更新配置
享受你的个性化光标雨点效果吧!🌧️✨

View File

@@ -0,0 +1,204 @@
# 🎛️ 光标雨点效果参数控制指南
## 📍 参数控制位置
### 1. **快速修改** - `docs/.vitepress/theme/index.ts`
直接在主题文件中自定义参数:
```typescript
// 完全自定义配置
const customConfig = {
maxDrops: 30, // 🌧️ 雨滴数量 (建议: 10-50)
color: 'rgba(173, 216, 230, 0.7)', // 🎨 颜色
duration: [1.0, 2.0], // ⏱️ 下落时间范围(秒)
dropSize: [3, 8], // 📏 雨滴大小范围(像素)
delay: 100, // ⏰ 鼠标移动延迟(毫秒)
zIndex: 1000, // 📐 层级
enabled: true // 🔄 是否启用
}
const rainEffect = initCursorRainForVitePress(customConfig)
```
### 2. **预设选择** - 使用现有预设
```typescript
// 选择预设
const config = rainPresets.fancy // 华丽模式
const config = rainPresets.light // 轻量模式
const config = rainPresets.pink // 粉色主题
const config = rainPresets.green // 绿色主题
const config = rainPresets.gold // 金色主题
```
### 3. **预设修改** - `docs/.vitepress/theme/cursor-rain-config.ts`
创建新预设或修改现有预设:
```typescript
export const rainPresets = {
// 添加你的自定义预设
myCustom: {
maxDrops: 35,
color: 'rgba(255, 100, 100, 0.8)',
duration: [0.8, 1.5],
dropSize: [2, 10],
delay: 80,
zIndex: 1000
} as RainDropOptions,
// 修改现有预设
default: {
maxDrops: 20, // 从25改为20
// ... 其他参数
}
}
```
## 🎯 详细参数说明
### 核心参数
| 参数 | 类型 | 默认值 | 说明 | 推荐范围 |
|------|------|--------|------|----------|
| `maxDrops` | number | 25 | 同时显示的最大雨滴数量 | 5-50 |
| `color` | string | 'rgba(173,216,230,0.6)' | 雨滴颜色(支持任何CSS颜色) | - |
| `duration` | [min,max] | [1.0, 2.0] | 下落时间范围(秒) | [0.5, 3.0] |
| `dropSize` | [min,max] | [3, 8] | 雨滴大小范围(像素) | [1, 15] |
| `delay` | number | 100 | 鼠标移动到雨滴出现的延迟(毫秒) | 50-300 |
### 高级参数
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `zIndex` | 雨滴容器的层级 | 1000 |
| `enabled` | 是否启用效果 | true |
| `container` | 效果容器元素 | document.body |
## 🎨 视觉效果参数
### 雨滴形状控制 (CSS)
`docs/.vitepress/theme/cursor-rain-styles.css` 中:
```css
.cursor-rain-container .rain-drop {
/* 🔸 雨滴形状 - 数值越大越尖锐 */
border-radius: 50% 50% 50% 50% / 90% 90% 10% 10% !important;
/* 🌟 发光效果 */
box-shadow: 0 0 6px rgba(173, 216, 230, 0.4);
/* 🌫️ 模糊效果 */
filter: blur(0.5px);
}
```
### 雨滴尺寸控制 (JavaScript源码)
`cursor-rain/src/CursorRainEffect.ts` 中:
```javascript
// 第177-178行
width: size * 0.3, // 🔸 宽度倍数 (越小越细)
height: size * 4, // 🔸 高度倍数 (越大越长)
```
## 🚀 实时调试
### 浏览器控制台调试
```javascript
// 🔄 更新配置
window.rainEffect.updateOptions({
maxDrops: 50,
color: 'rgba(255, 99, 99, 0.8)',
duration: [0.5, 1.0]
})
// ⏸️ 暂停效果
window.rainEffect.disable()
// ▶️ 恢复效果
window.rainEffect.enable()
```
### 动态切换主题
```javascript
// 需要先导入switchRainTheme函数
import { switchRainTheme } from './cursor-rain-config'
// 🎨 切换颜色主题
switchRainTheme(window.rainEffect, 'pink')
switchRainTheme(window.rainEffect, 'green')
switchRainTheme(window.rainEffect, 'gold')
```
## 🎛️ 常用调整场景
### 🐌 性能优化 (减少卡顿)
```typescript
const performanceConfig = {
maxDrops: 10, // 减少数量
duration: [2.0, 3.0], // 增加时间
delay: 200, // 增加延迟
dropSize: [2, 4] // 减小尺寸
}
```
### 🌪️ 华丽效果 (更多雨滴)
```typescript
const fancyConfig = {
maxDrops: 50, // 增加数量
duration: [0.5, 1.2], // 减少时间
delay: 30, // 减少延迟
dropSize: [4, 12] // 增大尺寸
}
```
### 📱 移动端优化
```typescript
const mobileConfig = {
maxDrops: 8, // 大幅减少
duration: [2.5, 4.0], // 增加时间
delay: 300, // 增加延迟
dropSize: [2, 5] // 减小尺寸
}
```
### 🌈 彩色雨滴
```typescript
// 多种颜色随机
const colors = [
'rgba(255, 99, 99, 0.7)', // 红
'rgba(99, 255, 99, 0.7)', // 绿
'rgba(99, 99, 255, 0.7)', // 蓝
'rgba(255, 255, 99, 0.7)' // 黄
]
// 在JavaScript中需要修改源码来支持随机颜色
```
## 🔧 修改步骤
1. **选择修改方式**
- 简单调整 → 修改 `index.ts`
- 创建预设 → 修改 `cursor-rain-config.ts`
- 视觉效果 → 修改 `cursor-rain-styles.css`
- 深度定制 → 修改源码 `CursorRainEffect.ts`
2. **测试效果**
- 保存文件后VitePress会自动热重载
- 或在浏览器控制台实时调试
3. **优化性能**
- 移动端减少 `maxDrops`
- 低性能设备增加 `delay``duration`
享受你的个性化雨滴效果!🌧️✨

7
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"@element-plus/icons-vue": "^2.3.1",
"@sugarat/theme": "0.5.6",
"element-plus": "^2.7.2",
"gsap": "^3.13.0",
"vue": "3.5.12"
},
"devDependencies": {
@@ -3504,6 +3505,12 @@
"node": ">=6.0"
}
},
"node_modules/gsap": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz",
"integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",

View File

@@ -13,6 +13,7 @@
"@element-plus/icons-vue": "^2.3.1",
"@sugarat/theme": "0.5.6",
"element-plus": "^2.7.2",
"gsap": "^3.13.0",
"vue": "3.5.12"
},
"directories": {