From 552bd91e03fa8c0f38f47e6059ec21585baf3507 Mon Sep 17 00:00:00 2001 From: handsomezhuzhu <2658601135@qq.com> Date: Mon, 15 Sep 2025 18:19:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E9=9B=A8=E5=85=89=E6=A0=87=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cursor-rain | 1 + .../cursor-rain/CursorRainEffect.d.ts | 28 ++ docs/.vitepress/cursor-rain/index.d.ts | 25 ++ docs/.vitepress/cursor-rain/index.esm.js | 307 +++++++++++++++++ docs/.vitepress/cursor-rain/index.js | 315 ++++++++++++++++++ docs/.vitepress/cursor-rain/types.d.ts | 71 ++++ docs/.vitepress/theme/cursor-rain-config.ts | 125 +++++++ docs/.vitepress/theme/cursor-rain-styles.css | 108 ++++++ docs/.vitepress/theme/index.ts | 38 ++- otherdocs/cursor-rain-guide.md | 195 +++++++++++ otherdocs/cursor-rain-parameter-guide.md | 204 ++++++++++++ package-lock.json | 7 + package.json | 1 + 13 files changed, 1424 insertions(+), 1 deletion(-) create mode 160000 cursor-rain create mode 100644 docs/.vitepress/cursor-rain/CursorRainEffect.d.ts create mode 100644 docs/.vitepress/cursor-rain/index.d.ts create mode 100644 docs/.vitepress/cursor-rain/index.esm.js create mode 100644 docs/.vitepress/cursor-rain/index.js create mode 100644 docs/.vitepress/cursor-rain/types.d.ts create mode 100644 docs/.vitepress/theme/cursor-rain-config.ts create mode 100644 docs/.vitepress/theme/cursor-rain-styles.css create mode 100644 otherdocs/cursor-rain-guide.md create mode 100644 otherdocs/cursor-rain-parameter-guide.md diff --git a/cursor-rain b/cursor-rain new file mode 160000 index 0000000..583adeb --- /dev/null +++ b/cursor-rain @@ -0,0 +1 @@ +Subproject commit 583adeb0f7317d9ad354bf601a3d421452554f0c diff --git a/docs/.vitepress/cursor-rain/CursorRainEffect.d.ts b/docs/.vitepress/cursor-rain/CursorRainEffect.d.ts new file mode 100644 index 0000000..d47eaf6 --- /dev/null +++ b/docs/.vitepress/cursor-rain/CursorRainEffect.d.ts @@ -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): void; + private createRainContainer; + private preCreateDrops; + private createDropElement; + private setupEventListeners; + private removeEventListeners; + private createRainAtPosition; + private getAvailableDrop; + private returnDropToPool; + private randomBetween; + private throttle; + private cleanup; +} diff --git a/docs/.vitepress/cursor-rain/index.d.ts b/docs/.vitepress/cursor-rain/index.d.ts new file mode 100644 index 0000000..9db4c5c --- /dev/null +++ b/docs/.vitepress/cursor-rain/index.d.ts @@ -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; diff --git a/docs/.vitepress/cursor-rain/index.esm.js b/docs/.vitepress/cursor-rain/index.esm.js new file mode 100644 index 0000000..c53d0ea --- /dev/null +++ b/docs/.vitepress/cursor-rain/index.esm.js @@ -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 }; diff --git a/docs/.vitepress/cursor-rain/index.js b/docs/.vitepress/cursor-rain/index.js new file mode 100644 index 0000000..d7f2749 --- /dev/null +++ b/docs/.vitepress/cursor-rain/index.js @@ -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; diff --git a/docs/.vitepress/cursor-rain/types.d.ts b/docs/.vitepress/cursor-rain/types.d.ts new file mode 100644 index 0000000..ec0ebb4 --- /dev/null +++ b/docs/.vitepress/cursor-rain/types.d.ts @@ -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): void; +} diff --git a/docs/.vitepress/theme/cursor-rain-config.ts b/docs/.vitepress/theme/cursor-rain-config.ts new file mode 100644 index 0000000..a46f856 --- /dev/null +++ b/docs/.vitepress/theme/cursor-rain-config.ts @@ -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}`) + } + } +} diff --git a/docs/.vitepress/theme/cursor-rain-styles.css b/docs/.vitepress/theme/cursor-rain-styles.css new file mode 100644 index 0000000..38e8740 --- /dev/null +++ b/docs/.vitepress/theme/cursor-rain-styles.css @@ -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%); +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 83e668e..20b415f 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -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 + } + } + } + } +} diff --git a/otherdocs/cursor-rain-guide.md b/otherdocs/cursor-rain-guide.md new file mode 100644 index 0000000..6eb81af --- /dev/null +++ b/otherdocs/cursor-rain-guide.md @@ -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()` 实时更新配置 + +享受你的个性化光标雨点效果吧!🌧️✨ diff --git a/otherdocs/cursor-rain-parameter-guide.md b/otherdocs/cursor-rain-parameter-guide.md new file mode 100644 index 0000000..cdd0986 --- /dev/null +++ b/otherdocs/cursor-rain-parameter-guide.md @@ -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` + +享受你的个性化雨滴效果!🌧️✨ diff --git a/package-lock.json b/package-lock.json index 3a7992d..3ce57ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index b3affd3..2139589 100644 --- a/package.json +++ b/package.json @@ -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": {