MCBBS Wiki欢迎您共同参与编辑!在参与编辑之前请先阅读Wiki方针。
如果在编辑的过程中遇到了什么问题,可以去讨论板提问。
为了您能够无阻碍地参与编辑 未验证/绑定过邮箱的用户,请尽快绑定/验证。
MCBBS Wiki GitHub群组已上线!
您可以在回声洞中发表吐槽!
服务器状态监控。点击进入
本站由MCBBS用户自行搭建,与MCBBS及东银河系漫游指南(北京)科技有限公司没有从属关系。点此了解 MCBBS Wiki 不是什么>>
微件:SaltFirework:修订间差异
跳到导航
跳到搜索
Salt lovely(留言 | 贡献) 小 (添加了自定义烟花颜色色相,颜色波动范围,粒子数量三种自定义参数) |
Salt lovely(留言 | 贡献) 小 (使用TS重写,开源在 github.com/mcbbs-wiki/mcbbs-wiki-widget-repo,换了一种烟花粒子扩散的算法,现在粒子不会聚集成一个环了) |
||
第8行: | 第8行: | ||
* Inspired By: https://codepen.io/jackrugile/pen/acAgx Author Jack Rugile |
* Inspired By: https://codepen.io/jackrugile/pen/acAgx Author Jack Rugile |
||
*/ |
*/ |
||
"use strict"; |
|||
(function () { |
|||
(() => { |
|||
// 阻止重复调用 |
|||
// src/utils/utils.ts |
|||
if (document.getElementById('saltFireWorkCanvas')) return |
|||
function docReady(fn) { |
|||
// 参数 |
|||
if (document.readyState === "loading") { |
|||
// 颜色范围 |
|||
window.addEventListener("DOMContentLoaded", fn); |
|||
const hueRange = (function () { |
|||
} else { |
|||
let defaultValue = (function () { |
|||
fn(); |
|||
for (let i = 1; i < 361; i++) |
|||
x.push(i) |
|||
return x |
|||
})() |
|||
let e = document.getElementById('saltForeworkHueRange') |
|||
if (!e) return defaultValue |
|||
let c = e.textContent.replace(/[\s\n_]+/g, '').replace(/[\;\/\|\/\\,;、\-]+/g, ',').split(',').map((v) => parseInt(v)).filter(Boolean).filter((v) => v > 0 && v < 361) |
|||
if (!c || c.length < 1 || c.length > 360) return defaultValue |
|||
return c |
|||
})()// [0, 360] |
|||
// 颜色变化区间 |
|||
const hueDiff = (function () { |
|||
let e = document.getElementById('saltForeworkHueDiff') |
|||
if (!e) return 30 |
|||
let c = parseInt(e.textContent) |
|||
if (isNaN(c) || c < 0 || c > 180) return 30 |
|||
return c |
|||
})() |
|||
// 粒子效果数量 |
|||
const count = (function () { |
|||
let e = document.getElementById('saltForeworkCount') |
|||
if (!e) return 110 |
|||
let c = parseInt(e.textContent) |
|||
if (isNaN(c) || c < 1 || c > 500) return 110 |
|||
return c |
|||
})() |
|||
const baseRange = [1, 4] // 粒子大小 |
|||
const speedMutiply = 6 // 粒子速度范围 |
|||
const radius = 1.7 // 粒子扩散半径比粒子速度 |
|||
const baseSpeed = 0.2 // 粒子最低速度 |
|||
const fallSpeed = 1.1 // 粒子下坠速度 |
|||
const tail = 15 // 尾迹 |
|||
/**创建canvas */ |
|||
let canvas = document.createElement('canvas') |
|||
/**获取canvas绘图区域 */ |
|||
let context = canvas.getContext('2d') |
|||
/**烟花粒子数组 */ |
|||
let particles = [] |
|||
/**记录剩余粒子数量,在合适的时机清理画布 */ |
|||
let lastLength = 0, zeroFrame = 0 |
|||
init() // 初始化 |
|||
function init() { |
|||
canvas.id = 'saltFireWorkCanvas' |
|||
canvas.style.left = '0' |
|||
canvas.style.top = '0' |
|||
canvas.style.position = 'fixed' |
|||
canvas.style.pointerEvents = 'none' // 不干扰鼠标 |
|||
canvas.style.zIndex = '99999' // 置于顶层 |
|||
document.body.appendChild(canvas) |
|||
resizeCanvas() |
|||
window.addEventListener('resize', resizeCanvas, false) |
|||
clearCanvas() |
|||
tick() // 开始tick |
|||
document.addEventListener('mousedown', function (e) { createFireworks(e.clientX, e.clientY) }) // 监听事件 |
|||
} |
} |
||
} |
|||
/**清理绘图区 */ |
|||
function randomChoice(arr) { |
|||
if (arr.length < 1) { |
|||
context.fillStyle = 'rgba(255,255,255,0)' |
|||
return null; |
|||
context.fillRect(0, 0, canvas.width, canvas.height) |
|||
} |
} |
||
return arr[Math.floor(Math.random() * arr.length)]; |
|||
/**重设绘图区大小*/ |
|||
} |
|||
function resizeCanvas() { |
|||
canvas.width = window.innerWidth |
|||
// widget/SaltFirework/widget.ts |
|||
canvas.height = window.innerHeight |
|||
if (document.getElementById("saltFireWorkCanvas")) |
|||
throw new Error("同一页面中只能有一个烟花"); |
|||
//! 颜色范围 |
|||
var hueRange; |
|||
//! 颜色变化区间 |
|||
var hueDiff; |
|||
//! 粒子效果数量 |
|||
var count; |
|||
var baseRange = [1, 4]; |
|||
var baseSpeed = [0.3, 2, 3]; |
|||
var fallSpeed = 1.1 / 60; |
|||
var fadeSpeed = 0.65; |
|||
var tail = 15; |
|||
var canvas = document.createElement("canvas"); |
|||
var context = canvas.getContext("2d"); |
|||
var particles = []; |
|||
var lastLength = 0; |
|||
var zeroFrame = 0; |
|||
docReady(init); |
|||
function init() { |
|||
const getValue = (id, defaultValue, min, max) => { |
|||
const e = document.getElementById(id); |
|||
if (!e) |
|||
return defaultValue; |
|||
const c = parseInt(e.textContent || ""); |
|||
if (isNaN(c) || c < min || c > max) |
|||
return defaultValue; |
|||
return c; |
|||
}; |
|||
hueRange = (() => { |
|||
const defaultValue = (() => { |
|||
var x = []; |
|||
for (let i = 1; i < 361; i++) |
|||
x.push(i); |
|||
return x; |
|||
})(); |
|||
const e = document.getElementById("saltFireworkHueRange"); |
|||
if (!e) |
|||
return defaultValue; |
|||
const c = (e.textContent || "").replace(/[\s\n_]+/g, "").replace(/[\;\/\|\/\\,;、\-]+/g, ",").split(",").map((v) => parseInt(v)).filter(Boolean).filter((v) => v > 0 && v < 361); |
|||
if (!c || c.length < 1 || c.length > 360) |
|||
return defaultValue; |
|||
return c; |
|||
})(); |
|||
hueDiff = getValue("saltFireworkHueDiff", 30, 0, 180); |
|||
count = getValue("saltFireworkCount", 110, 1, 500); |
|||
canvas.id = "saltFireWorkCanvas"; |
|||
canvas.style.left = "0"; |
|||
canvas.style.top = "0"; |
|||
canvas.style.position = "fixed"; |
|||
canvas.style.pointerEvents = "none"; |
|||
canvas.style.zIndex = "99999"; |
|||
document.body.appendChild(canvas); |
|||
resizeCanvas(); |
|||
window.addEventListener("resize", resizeCanvas, false); |
|||
tick(); |
|||
document.addEventListener("mousedown", function(e) { |
|||
createFireworks(e.clientX, e.clientY); |
|||
}); |
|||
} |
|||
function resizeCanvas() { |
|||
canvas.width = window.innerWidth; |
|||
canvas.height = window.innerHeight; |
|||
} |
|||
function rightRandom(base, size) { |
|||
return base + (Math.random() * size - Math.random() * size) / 2; |
|||
} |
|||
function createFireworks(x, y) { |
|||
let hue = randomChoice(hueRange); |
|||
for (let i = 0; i < count; i++) { |
|||
const spd = rightRandom((baseSpeed[1] + baseSpeed[0]) / 2, baseSpeed[1] - baseSpeed[0]); |
|||
const rad = Math.random() * 2 * Math.PI; |
|||
particles.push({ |
|||
x, |
|||
y, |
|||
spdX: Math.cos(rad) * spd, |
|||
spdY: Math.sin(rad) * spd, |
|||
spdFall: baseSpeed[2], |
|||
size: rightRandom((baseRange[1] + baseRange[0]) / 2, baseRange[1] - baseRange[0]), |
|||
hue: hueRandom(), |
|||
bright: rightRandom(72, 16), |
|||
alpha: rightRandom(75, 30) |
|||
}); |
|||
} |
} |
||
function hueRandom() { |
|||
/**创建烟花*/ |
|||
let h = Math.floor(rightRandom(hue, hueDiff)); |
|||
function createFireworks(x, y) { |
|||
if (h > 360) |
|||
h -= 360; |
|||
else if (h < 0) |
|||
h += 360; |
|||
let spd = Math.random() * speedMutiply + baseSpeed |
|||
return h; |
|||
rad: Math.random() * 2 * Math.PI, // 弧度 |
|||
x: x, |
|||
y: y, |
|||
speed: spd, |
|||
radius: spd * radius, |
|||
size: Math.floor(Math.random() * (baseRange[1] - baseRange[0])) + baseRange[0], |
|||
hue: hueRandom(), |
|||
bright: Math.floor(Math.random() * 16) + 65, |
|||
alpha: Math.floor(Math.random() * 51) + 50, |
|||
} |
|||
particles.push(p) |
|||
} |
|||
function hueRandom() { |
|||
let h = Math.floor(Math.random() * hueDiff + hue - hueDiff) |
|||
if (h > 360) |
|||
h -= 360 |
|||
else if (h < 0) |
|||
h += 360 |
|||
return h |
|||
} |
|||
} |
} |
||
} |
|||
/**主过程*/ |
|||
function drawParticles() { |
|||
if (!particles.length) |
|||
return; |
|||
let p = particles[i] |
|||
context.globalCompositeOperation = "lighter"; |
|||
if (!p) |
|||
for (let i = 0; i < particles.length; i++) { |
|||
let p = particles[i]; |
|||
if (!p) |
|||
p.y += Math.sin(p.rad) * p.radius + fallSpeed |
|||
continue; |
|||
p.radius *= 1 - p.speed / 100 |
|||
p.x += p.spdX * p.spdFall; |
|||
p.y += p.spdY * p.spdFall; |
|||
p.spdY += fallSpeed; |
|||
context.arc(p.x, p.y, p.size, 0, Math.PI * 2, false) |
|||
p.spdFall *= 0.978; |
|||
p.alpha -= fadeSpeed; |
|||
context.fillStyle = `hsla(${p.hue},100%,${p.bright}%,${p.alpha / 100})` |
|||
context.beginPath(); |
|||
context.arc(p.x, p.y, p.size, 0, Math.PI * 2, false); |
|||
if (p.alpha < 0.75) // 标记已经透明到看不见的粒子 |
|||
context.closePath(); |
|||
particles[i] = null |
|||
context.fillStyle = `hsla(${p.hue},100%,${p.bright}%,${p.alpha / 100})`; |
|||
} |
|||
context.fill(); |
|||
if (lastLength == 0 && particles.length == 0) { |
|||
//! 标记已经透明到看不见的粒子 |
|||
zeroFrame += 1 |
|||
if (p.alpha < fadeSpeed) |
|||
if (zeroFrame == 30) // 连续30帧没有粒子 |
|||
particles[i] = null; |
|||
canvas.height = window.innerHeight // 利用画布重设大小清除内容的特性来清理里面的东西 |
|||
} |
|||
else { zeroFrame = 0 } |
|||
lastLength = particles.length |
|||
} |
} |
||
if (lastLength === 0 && particles.length === 0) { |
|||
/**画出尾迹 */ |
|||
zeroFrame += 1; |
|||
function drawTail() { |
|||
if (zeroFrame === 30) |
|||
context.globalCompositeOperation = 'destination-out' // 保留前一刻的图案作为尾迹 |
|||
canvas.height = window.innerHeight; |
|||
} else { |
|||
context.fillRect(0, 0, canvas.width, canvas.height) |
|||
zeroFrame = 0; |
|||
context.globalCompositeOperation = 'lighter' |
|||
} |
} |
||
lastLength = particles.length; |
|||
/**清理已经消失的粒子*/ |
|||
} |
|||
function clearParticles() { |
|||
function drawTail() { |
|||
let cp = [] |
|||
if (zeroFrame >= 30) |
|||
return; |
|||
//! 保留前一刻的图案作为尾迹 |
|||
cp.push(p) |
|||
context.globalCompositeOperation = "destination-out"; |
|||
particles = cp |
|||
context.fillStyle = `rgba(0,0,0,${1 / tail})`; |
|||
context.fillRect(0, 0, canvas.width, canvas.height); |
|||
} |
|||
function clearParticles() { |
|||
if (!particles.length) |
|||
return; |
|||
let cp = []; |
|||
for (let p of particles) |
|||
if (p) |
|||
cp.push(p); |
|||
if (cp.length !== particles.length) |
|||
particles = cp; |
|||
} |
|||
function tick() { |
|||
//! 画尾迹 -> 画这一帧的粒子 -> 删除运算完毕的粒子 |
|||
drawTail(); |
|||
drawParticles(); |
|||
clearParticles(); |
|||
if (false) { |
|||
const el = document.getElementById("saltFWInfo"); |
|||
if (el) { |
|||
el.innerHTML = particles.map((p) => JSON.stringify(p)).join("<br/>"); |
|||
} |
|||
} |
} |
||
requestAnimationFrame(tick); |
|||
/**高频调用*/ |
|||
} |
|||
function tick() { |
|||
})(); |
|||
// 画尾迹 -> 画这一帧的粒子 -> 删除运算完毕的粒子 |
|||
drawTail() |
|||
drawParticles() |
|||
clearParticles() |
|||
requestAnimationFrame(tick) |
|||
} |
|||
/**随机选择 */ |
|||
function randomChoice(arr) { |
|||
if (arr.length < 1) { |
|||
return null; |
|||
} |
|||
return arr[Math.floor(Math.random() * arr.length)]; |
|||
} |
|||
})() |
|||
</script></includeonly> |
</script></includeonly> |
2022年7月24日 (日) 20:15的最新版本