yoone-snow/js/snow-canvas.js

173 lines
7.1 KiB
JavaScript

(function(){
// 初始化函数 用于启动雪花效果
function init(){
const canvas = document.getElementById('effectiveAppsSnow');
// 条件判断 如果未找到画布元素则不执行
if (!canvas) return;
const context = canvas.getContext('2d');
let viewportWidth = window.innerWidth;
let viewportHeight = window.innerHeight;
const devicePixelRatio = window.devicePixelRatio || 1;
// 读取首页显示时长设置 单位为秒 0 表示无限
const displayDurationSeconds = (window.YooneSnowSettings && typeof window.YooneSnowSettings.displayDurationSeconds !== 'undefined')
? Math.max(0, parseInt(window.YooneSnowSettings.displayDurationSeconds, 10) || 0)
: 0;
// 记录启动时间 用于计算已运行时间
const startTimestamp = performance.now();
// 标记是否已达到显示时长 到达后不再生成新粒子并不再重生
let hasReachedDuration = false;
// 读取选中的形状集合 简化为仅使用复选框集合
const selectedShapes = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.selectedShapes) && window.YooneSnowSettings.selectedShapes.length > 0)
? window.YooneSnowSettings.selectedShapes
: ['dot','flake'];
// 读取媒体形状列表 用于将媒体图片作为形状参与渲染
const mediaItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.mediaItems))
? window.YooneSnowSettings.mediaItems
: [];
function resizeCanvas(){
viewportWidth = window.innerWidth;
viewportHeight = window.innerHeight;
const displayWidth = viewportWidth;
const displayHeight = viewportHeight;
canvas.style.width = displayWidth + 'px';
canvas.style.height = displayHeight + 'px';
canvas.width = Math.floor(displayWidth * devicePixelRatio);
canvas.height = Math.floor(displayHeight * devicePixelRatio);
context.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
}
resizeCanvas();
const snowflakeCount = Math.floor(Math.min(400, Math.max(120, (viewportWidth * viewportHeight) / 12000)));
const snowflakes = [];
for (let i = 0; i < snowflakeCount; i++){
// 为每个雪花确定形状类型 从选中集合与媒体列表的合并池中随机选择
const poolSize = selectedShapes.length + mediaItems.length;
const randomIndex = Math.floor(Math.random() * Math.max(1, poolSize));
let chosenType = 'dot';
let chosenImageUrl = null;
if (randomIndex < selectedShapes.length) {
// 条件判断 使用普通形状类型
chosenType = selectedShapes[randomIndex];
} else if (mediaItems.length > 0) {
// 条件判断 使用媒体图片作为形状 并记录其 URL
const mediaIndex = randomIndex - selectedShapes.length;
chosenType = 'media_image';
chosenImageUrl = mediaItems[mediaIndex % mediaItems.length];
}
snowflakes.push({
positionX: Math.random() * viewportWidth,
positionY: Math.random() * viewportHeight,
radius: Math.random() * 2 + 1,
driftSpeed: Math.random() * 0.6 + 0.4,
swingAmplitude: Math.random() * 0.8 + 0.2,
shapeType: chosenType,
imageUrl: chosenImageUrl,
// 标记该粒子是否已经移出视口 用于停止后清理
outOfView: false
});
}
function updateSnowflakes(){
for (let j = 0; j < snowflakes.length; j++){
const flake = snowflakes[j];
flake.positionY += flake.driftSpeed * 2 + flake.radius * 0.25;
flake.positionX += Math.sin(flake.positionY * 0.01) * flake.swingAmplitude;
if (flake.positionY > viewportHeight + 5){
// 条件判断 如果尚未达到时长则重生 如果已达到则标记为移出视口
if (!hasReachedDuration){
flake.positionY = -5;
flake.positionX = Math.random() * viewportWidth;
flake.radius = Math.random() * 2 + 1;
flake.driftSpeed = Math.random() * 0.6 + 0.4;
flake.swingAmplitude = Math.random() * 0.8 + 0.2;
} else {
flake.outOfView = true;
}
}
}
}
// 使用全局渲染注册表 根据形状类型选择渲染函数
function drawSnowflakes(){
context.clearRect(0, 0, viewportWidth, viewportHeight);
for (let k = 0; k < snowflakes.length; k++){
const flake = snowflakes[k];
// 条件判断 如果该粒子已移出视口则不再绘制
if (flake.outOfView){ continue; }
// 条件判断 如果是媒体图片类型则使用通用图像渲染
if (flake.shapeType === 'media_image' && flake.imageUrl){
const record = window.YooneSnowGetOrLoadImage ? window.YooneSnowGetOrLoadImage(flake.imageUrl) : { img: null, ready: false };
if (record && record.ready){
const targetHeight = flake.radius * 8;
const targetWidth = targetHeight;
window.YooneSnowDrawCenteredImage(context, record.img, flake.positionX, flake.positionY, targetWidth, targetHeight);
}
continue;
}
// 否则执行注册表中的形状渲染函数
const registry = window.YooneSnowShapeRenderers || {};
const renderer = registry[flake.shapeType] || registry['dot'];
if (typeof renderer === 'function'){
renderer(context, flake.positionX, flake.positionY, flake.radius);
}
}
}
// 清理停止函数 用于在达到设定时长后停止动画并释放资源
function cleanupStop(){
// 条件判断 如果存在窗口尺寸事件监听则移除
if (typeof onResize === 'function') {
window.removeEventListener('resize', onResize);
}
// 清空画布并隐藏元素
context.clearRect(0, 0, viewportWidth, viewportHeight);
canvas.style.display = 'none';
}
function animate(){
updateSnowflakes();
drawSnowflakes();
// 条件判断 如果设置了有限时长则在达到时长后不再生成新粒子并等待自然落出
if (displayDurationSeconds > 0 && !hasReachedDuration){
const elapsedSeconds = (performance.now() - startTimestamp) / 1000;
if (elapsedSeconds >= displayDurationSeconds){
hasReachedDuration = true;
}
}
// 条件判断 如果已达到时长且所有粒子都移出视口则执行清理停止
if (hasReachedDuration){
const allOut = snowflakes.every(function(f){ return f.outOfView; });
if (allOut){
cleanupStop();
return;
}
}
requestAnimationFrame(animate);
}
// 定义窗口尺寸事件处理器 以便在停止时移除
function onResize(){
// 条件判断 保证画布尺寸与视口一致
resizeCanvas();
viewportWidth = window.innerWidth;
viewportHeight = window.innerHeight;
}
window.addEventListener('resize', onResize);
animate();
}
// 条件判断 如果文档尚未加载则等待 DOMContentLoaded 事件
if (document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();