mirror of
https://github.com/handsomezhuzhu/handsomezhuzhu.github.io.git
synced 2026-02-20 20:00:14 +00:00
一些奇怪的更改
This commit is contained in:
@@ -1,71 +1,71 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
// 导入主题的配置
|
||||
import { blogTheme } from './blog-theme'
|
||||
|
||||
// 如果使用 GitHub/Gitee Pages 等公共平台部署
|
||||
// 通常需要修改 base 路径,通常为“/仓库名/”
|
||||
// 如果项目名已经为 name.github.io 域名,则不需要修改!
|
||||
// const base = process.env.GITHUB_ACTIONS === 'true'
|
||||
// ? '/vitepress-blog-sugar-template/'
|
||||
// : '/'
|
||||
|
||||
// Vitepress 默认配置
|
||||
// 详见文档:https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
// 继承博客主题(@sugarat/theme)
|
||||
extends: blogTheme,
|
||||
// base,
|
||||
lang: 'zh-cn',
|
||||
title: 'SIMON BLOG',
|
||||
description: 'Simon的博客,基于 vitepress 实现',
|
||||
lastUpdated: true,
|
||||
markdown: {
|
||||
math: true
|
||||
},
|
||||
// 详见:https://vitepress.dev/zh/reference/site-config#head
|
||||
head: [
|
||||
// 配置网站的图标(显示在浏览器的 tab 上)
|
||||
// ['link', { rel: 'icon', href: `${base}favicon.ico` }], // 修改了 base 这里也需要同步修改
|
||||
['link', { rel: 'icon', href: '/favicon.ico' }]
|
||||
],
|
||||
themeConfig: {
|
||||
// 展示 2,3 级标题在目录中
|
||||
outline: {
|
||||
level: [2, 3],
|
||||
label: '目录'
|
||||
},
|
||||
// 默认文案修改
|
||||
returnToTopLabel: '回到顶部',
|
||||
sidebarMenuLabel: '相关文章',
|
||||
lastUpdatedText: '上次更新于',
|
||||
|
||||
|
||||
|
||||
// 设置logo
|
||||
logo: '/logo.jpg',
|
||||
// editLink: {
|
||||
// pattern:
|
||||
// 'https://github.com/ATQQ/sugar-blog/tree/master/packages/blogpress/:path',
|
||||
// text: '去 GitHub 上编辑内容'
|
||||
// },
|
||||
nav: [
|
||||
{ text: '首页', link: '/' },
|
||||
{ text: '线路一', link: 'https://zhuzihan.com' },
|
||||
{ text: '线路二', link: 'https://zzh.codes' },
|
||||
{ text: '线路三', link: 'https://zzhdsgsss.xyz' },
|
||||
{ text: '导航页', link: 'http://home.zhuzihan.com/' },
|
||||
{ text: '主题仓库', link: 'https://github.com/ATQQ/sugar-blog/tree/master/packages/theme' },
|
||||
{ text: '关于作者', link: 'https://github.com/handsomezhuzhu' },
|
||||
{ text: 'OpenWebUI', link: 'https://ai.zzhdsgsss.xyz/' },
|
||||
{ text: 'AI API测活', link: 'https://api-test.zhuzihan.com/' }
|
||||
],
|
||||
socialLinks: [
|
||||
{
|
||||
icon: 'github',
|
||||
link: 'https://github.com/handsomezhuzhu'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
// 导入主题的配置
|
||||
import { blogTheme } from './blog-theme'
|
||||
|
||||
// 如果使用 GitHub/Gitee Pages 等公共平台部署
|
||||
// 通常需要修改 base 路径,通常为“/仓库名/”
|
||||
// 如果项目名已经为 name.github.io 域名,则不需要修改!
|
||||
// const base = process.env.GITHUB_ACTIONS === 'true'
|
||||
// ? '/vitepress-blog-sugar-template/'
|
||||
// : '/'
|
||||
|
||||
// Vitepress 默认配置
|
||||
// 详见文档:https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
// 继承博客主题(@sugarat/theme)
|
||||
extends: blogTheme,
|
||||
// base,
|
||||
lang: 'zh-cn',
|
||||
title: 'SIMON BLOG',
|
||||
description: 'Simon的博客,基于 vitepress 实现',
|
||||
lastUpdated: true,
|
||||
markdown: {
|
||||
math: true
|
||||
},
|
||||
// 详见:https://vitepress.dev/zh/reference/site-config#head
|
||||
head: [
|
||||
// 配置网站的图标(显示在浏览器的 tab 上)
|
||||
// ['link', { rel: 'icon', href: `${base}favicon.ico` }], // 修改了 base 这里也需要同步修改
|
||||
['link', { rel: 'icon', href: '/favicon.ico' }]
|
||||
],
|
||||
themeConfig: {
|
||||
// 展示 2,3 级标题在目录中
|
||||
outline: {
|
||||
level: [2, 3],
|
||||
label: '目录'
|
||||
},
|
||||
// 默认文案修改
|
||||
returnToTopLabel: '回到顶部',
|
||||
sidebarMenuLabel: '相关文章',
|
||||
lastUpdatedText: '上次更新于',
|
||||
|
||||
|
||||
|
||||
// 设置logo
|
||||
logo: '/logo.jpg',
|
||||
// editLink: {
|
||||
// pattern:
|
||||
// 'https://github.com/ATQQ/sugar-blog/tree/master/packages/blogpress/:path',
|
||||
// text: '去 GitHub 上编辑内容'
|
||||
// },
|
||||
nav: [
|
||||
{ text: '首页', link: '/' },
|
||||
{ text: '线路一', link: 'https://zhuzihan.com' },
|
||||
{ text: '线路二', link: 'https://zzh.codes' },
|
||||
{ text: '线路三', link: 'https://zzhdsgsss.xyz' },
|
||||
{ text: '导航页', link: 'http://home.zhuzihan.com/' },
|
||||
{ text: '主题仓库', link: 'https://github.com/ATQQ/sugar-blog/tree/master/packages/theme' },
|
||||
{ text: '关于作者', link: 'https://github.com/handsomezhuzhu' },
|
||||
{ text: 'OpenWebUI', link: 'https://ai.zzhdsgsss.xyz/' },
|
||||
{ text: 'AI API测活', link: 'https://api-test.zhuzihan.com/' }
|
||||
],
|
||||
socialLinks: [
|
||||
{
|
||||
icon: 'github',
|
||||
link: 'https://github.com/handsomezhuzhu'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,28 +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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
50
docs/.vitepress/cursor-rain/index.d.ts
vendored
50
docs/.vitepress/cursor-rain/index.d.ts
vendored
@@ -1,25 +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;
|
||||
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;
|
||||
|
||||
@@ -1,307 +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 };
|
||||
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 };
|
||||
|
||||
@@ -1,315 +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;
|
||||
'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;
|
||||
|
||||
142
docs/.vitepress/cursor-rain/types.d.ts
vendored
142
docs/.vitepress/cursor-rain/types.d.ts
vendored
@@ -1,71 +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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,125 +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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 光标雨点效果配置文件
|
||||
// 你可以在这里定义不同的配置预设
|
||||
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +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%);
|
||||
}
|
||||
/* 光标雨点效果自定义样式 */
|
||||
|
||||
/* 基础雨点样式 */
|
||||
.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%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user