JavaScript实现类似微信礼花算法

  • 预览
  • 前言
    • 关于标题使用`算法`二字说明
    • 关于`封装`
  • 实现
  • 代码
  • 使用
  • 参数
  • 思想
  • 遗留问题
  • 结束

预览

前言

关于标题使用算法二字说明

个人认为算法是解决某一问题的方法,怕理解不当,搜索维基百科得到以下结果(摘录):

算法(algorithm;算法),在数学(算学)和计算机科学之中,指一个被定义好的、计算机可施行其指示的有限步骤或次序,常用于计算、数据处理和自动推理。算法是有效方法,包含一系列定义清晰的指令,并可于有限的时间及空间内清楚的表述出来。

所以我认为并不是只有 排序、查找、最优解 之类的抽象代码程序,或更为复杂的 神经网络、深度学习、机器视觉 才称为算法
我想说的是,算法没那么神秘和高大上
你我在写代码解决问题或实现功能的模块中,就可能是在写算法
模块可以画成流程图,那么流程图其实就是算法的图形表现

关于封装

此代码在单独的 js 文件中,可直接导入页面调用

实现

使用了 canvas 画布实现图像绘制
抛物模式使用了简单的自由落体物理公式
烟花模式则是直接位移
使用了 transform 使图形变形实现视觉飘落感
设计了良好的框架解耦

代码

  • 封装好的模块
/*** Title    : Js For Fireworks* Author   : Fc_404* Date     : 2021-09-14* Describe :*/const PIPI = 2 * Math.PI
const PIR = PIPI / 360const addCanvas = function () {var el = document.createElement('canvas')el.height = window.innerHeightel.width = window.innerWidthel.style.padding = 0el.style.margin = 0el.style.position = 'fixed'el.style.top = 0el.style.left = 0el.style.backgroundColor = 'rgba(0,0,0,1)'document.body.prepend(el)return el
}class Fireworks {//#region DEFINE// numberquantity = 20// anglerange = 30// numberspeed = 12// angleangle = 45// dotsposition = [0, 0]// arrcolors = ["#999999", "#CCCCCC"]// enum {range, value}colorsMode = 'range'// enum {firework, parabola}launchMode = 'parabola'// object eg.// {"arc":range, "ratio":num}// {"rect":[width, height], "ratio":num}// {"text":[text, size], "ratio":num}shape = [{ "arc": 20, "ratio": 1 },{ "rect": [20, 40], "ratio": 1 },{ "text": ["Firework", 20], "ratio": 1 }]// numgravity = 9.8// transformisTransform = true// object#spirits = []#spiritsDustbin = []//#endregion//#region INIT#newSpirits() {// recalculate ratiovar totalratio = 0for (var i in this.shape) {totalratio += this.shape[i].ratio}for (var i in this.shape) {this.shape[i].ratio = parseInt((this.shape[i].ratio * this.quantity) / totalratio)}// new spiritvar spirit = []for (var i in this.shape) {var items = JSON.parse(JSON.stringify(this.shape[i]))for (var ii = 0; ii < items.ratio; ++ii) {var iitem = JSON.parse(JSON.stringify(items))// Init position and directioniitem.x = this.position[0]iitem.y = this.position[1]// Init colorif (this.colorsMode == 'value') {iitem.color = this.colors[parseInt(Math.random() * this.colors.length)]} else if (this.colorsMode == 'range') {iitem.color = this.#getColor()}// Init angle and speediitem.angle = this.angle+ Math.random() * (this.range / 2)* (Math.random() > 0.5 ? 1 : -1)iitem.speed = this.speed+ Math.random() * this.speed* (this.launchMode == 'firework' ? 2 : 0.5)// Init Greaityif (this.launchMode == 'firework') {iitem.gravity = this.gravity+ Math.random()}// Calculation vertical and horizontal velocityiitem.verticalV = Math.sin(iitem.angle * PIR) * iitem.speediitem.horizontalV = Math.cos(iitem.angle * PIR) * iitem.speed// Init transformationiitem.transformation = [1, 0, 0, 1, 0, 0]spirit.push(iitem)}}this.#spirits.push([Date.now(), spirit, 0])}#getColor() {var groupL = parseInt(this.colors.length / 2)var group = parseInt(Math.random() * groupL)var hcolor = this.colors[group * 2].slice(1)var ecolor = this.colors[group * 2 + 1].slice(1)try {var hcolorR = parseInt(hcolor.slice(0, 2), 16)var hcolorG = parseInt(hcolor.slice(2, 4), 16)var hcolorB = parseInt(hcolor.slice(4, 6), 16)var ecolorR = parseInt(ecolor.slice(0, 2), 16)var ecolorG = parseInt(ecolor.slice(2, 4), 16)var ecolorB = parseInt(ecolor.slice(4, 6), 16)} catch (m) {throw new TypeError('Color must be #xxxxxx')}var colorR = parseInt(Math.random() *Math.abs(ecolorR - hcolorR) +(hcolorR < ecolorR ? hcolorR : ecolorR)).toString(16)if (colorR.length == 1) colorR = '0' + colorRvar colorG = parseInt(Math.random() *Math.abs(ecolorG - hcolorG) +(hcolorG < ecolorG ? hcolorG : ecolorG)).toString(16)if (colorG.length == 1) colorG = '0' + colorGvar colorB = parseInt(Math.random() *Math.abs(ecolorB - hcolorB) +(hcolorB < ecolorB ? hcolorB : ecolorB)).toString(16)if (colorB.length == 1) colorB = '0' + colorBreturn '#' + colorR + colorG + colorB}//#endregion//#region DRAW#draw() {this.ctx.clearRect(0, 0, window.innerWidth, window.innerHeight)for (var g in this.#spirits) {for (var i in this.#spirits[g][1]) {var item = this.#spirits[g][1][i]switch (Object.keys(item)[0]) {case 'arc':this.#drawArc(item)breakcase 'rect':this.#drawRect(item)breakcase 'text':this.#drawText(item)break;}}}}#drawArc(spirit) {this.ctx.beginPath()this.ctx.save()this.#transform(spirit)this.ctx.arc(spirit.x - spirit.y * spirit.transformation[2],spirit.y - spirit.x * spirit.transformation[1],spirit.arc,0, PIPI)this.ctx.fillStyle = spirit.colorthis.ctx.fill()this.ctx.strokeStyle = spirit.colorthis.ctx.stroke()this.ctx.closePath()this.ctx.restore()}#drawRect(spirit) {this.ctx.save()this.#transform(spirit)this.ctx.fillStyle = spirit.colorthis.ctx.fillRect(spirit.x - spirit.y * spirit.transformation[2],spirit.y - spirit.x * spirit.transformation[1],spirit.rect[0], spirit.rect[1])this.ctx.strokeStyle = spirit.colorthis.ctx.stroke()this.ctx.restore()}#drawText(spirit) {this.ctx.save()this.ctx.font = spirit.text[1] + 'px sans-serif'this.ctx.fillStyle = spirit.colorthis.#transform(spirit)this.ctx.fillText(spirit.text[0],spirit.x - spirit.y * spirit.transformation[2],spirit.y - spirit.x * spirit.transformation[1])this.ctx.strokeStyle = spirit.colorthis.ctx.stroke()this.ctx.restore()}#transform(spirit) {var offsetX = spirit.x - spirit.x * spirit.transformation[0]var offsetY = spirit.y - spirit.y * spirit.transformation[3]switch (Object.keys(spirit)[0]) {case 'rect':offsetX -= spirit.rect[1] * spirit.transformation[2]offsetY -= spirit.rect[1] * spirit.transformation[1]breakcase 'arc':offsetX -= spirit.arc * spirit.transformation[2] * 2offsetY -= spirit.arc * spirit.transformation[1] * 2breakcase 'text':offsetX -= spirit.text[1] * spirit.transformation[2]offsetY -= spirit.text[1] * spirit.transformation[1]}this.ctx.setTransform()this.ctx.transform(spirit.transformation[0],spirit.transformation[1],spirit.transformation[2],spirit.transformation[3],spirit.transformation[4] + offsetX,spirit.transformation[5] + offsetY)}//#endregion//#region ENGINE#moveEngine() {for (var g in this.#spirits) {var msec = (Date.now() - this.#spirits[g][0])var time = msec / 1000var spiritDustbin = []for (var i in this.#spirits[g][1]) {var item = this.#spirits[g][1][i]var verticalS, horizontalSswitch (this.launchMode) {case 'parabola':verticalS = item.verticalV * time- 0.5 * (this.gravity)* Math.pow(time, 2)horizontalS = item.horizontalV * time * 0.1breakcase 'firework':if (time < 0.1) {verticalS = item.verticalV * time * 64horizontalS = item.horizontalV * time * 64}else {horizontalS = 0verticalS = item.gravity * time * -1}break}item.x += horizontalSitem.y -= verticalSvar topPosition = 0switch (Object.keys(item)[0]) {case 'arc':topPosition = item.y - item.arcbreakcase 'rect':topPosition = item.y - item.rect[1]breakcase 'text':topPosition = item.y - item.text[1] * PIPIbreak}if (topPosition > window.innerHeight) {spiritDustbin.push(i)}}this.#spiritsDustbin.push([g, spiritDustbin])}}#transformEngine() {if (!this.isTransform)returnfor (var g in this.#spirits) {var msec = (Date.now() - this.#spirits[g][0])var time = msec / 1000if (time - this.#spirits[g][2] < 0.1)continuefor (var i in this.#spirits[g][1]) {var item = this.#spirits[g][1][i]if (!('polarity' in item)) {if (Math.random() > 0.2)continueitem.polarity = false}var c = item.transformation[2]var d = item.transformation[3]c *= 10d *= 10c -= 2if (c < -20) {c = 18}d += (item.polarity ? 1 : -1)if (d <= 0) {item.polarity = trued = 0}else if (d >= 10) {item.polarity = falsed = 10}item.transformation[2] = c / 10item.transformation[3] = d / 10}this.#spirits[g][2] = time}}#clearDustbin() {for (var g in this.#spiritsDustbin) {var group = this.#spiritsDustbin[g][0]var dustbin = this.#spiritsDustbin[g][1]for (var i in dustbin) {this.#spirits[group][1].splice(dustbin[i] - i, 1)}}this.#spiritsDustbin.splice(0, this.#spiritsDustbin.length)var spiritL = this.#spirits.lengthfor (var i = 0; i < spiritL; ++i) {if (this.#spirits[i][1].length == 0) {this.#spirits.splice(i, 1)i--spiritL--}}}//#endregionconstructor() {this.el = addCanvas()this.ctx = this.el.getContext("2d")}launch() {const self = thisthis.#newSpirits()var procedure = function () {self.#moveEngine()self.#transformEngine()self.#draw()self.#clearDustbin()if (self.#spirits.length > 0)requestAnimationFrame(procedure)}procedure()}
}export default {Fireworks,
}
  • 页面代码
<!DOCTYPE html>
<html><head><title>Fireworks</title></head><body></body>
</html><script type="module">import f from './Fireworks.js'var a = new f.Fireworks()a.quantity = 120a.speed = 12a.angle = 45a.range = 30a.gravity = 2a.launchMode = 'firework'a.position = [100, 400]a.shape = [{ "text": ['English', 12], "ratio": 1 },{ "text": ['Chinese', 12], "ratio": 1 },{ "arc": 6, "ratio": 2 },{ "rect": [6, 12], "ratio": 2 }]a.launch()a.el.onclick = () => { a.launch() }
</script>

使用

  1. 先将模块导入项目
    import f from './Fireworks.js'
  2. new 一个对象
    var a = new f.Fireworks()
  3. 配置参数(下边有详细参数列表)
  4. 发射
    a.launch()

参数

参数名 类型 描述
quantity number 碎片数量,建议大于shape数组参数的长度
range number 发射范围,以angle参数为中心的角度领域
speed number 发射初速度,此参数仅对抛物模式有作用
angle number 发射角度,角度制支持正负
position array[number, number] 发射初始位置,以左上为(0,0)点的坐标系
colors array[string, …] 颜色,RGB制(仅当颜色模式为value时才可为任意颜色制)
colorsMode string 颜色模式,仅有range范围模式和value值模式
launchMode string 发射模式,仅有firework烟花模式和parabola抛物模式
shape array[object, …] 碎片形状,仅支持arc圆形rect矩形text文字三类对象(下面表格详解)
gravity number 环境重力,对两种发射模式的落体运动有影响
isTransform bool 飘落模式,为真则会变形以实现飘落
  • shape对象结构
对象 结构
arc {“arc”: 半径, “ratio”: 数量比例}
rect {“rect”: [宽, 高], “ratio”: 数量比例}
text {“text”: [文字, 大小], “ratio”: 数量比例}

思想

解决这个问题我首先想到的是:使项目结构化、易用化、可扩展、易维护
所以我封装起来,只需要导入模块,new一个对象即可,当然也可以自定义参数,使模块个性化

在写代码的过程中,先大体分好需要做那些动作,然后分离出来,解耦合,然后针对每个动作再细分出可重复利用的代码,降低冗余
正如你所见,大体动作被#region符号折叠起来了,然后细分功能函数
在主流程函数launch()中,清晰的动作逻辑,使代码阅读性大大提高,便于维护,正因如此,为了实现多炮同屏同步,也轻松了很多

本来是想使用自由落体公式加阻力浮力参数去实现烟花效果,折腾了一下午无果,虽然有想法,但是就是实现不起来,还是说明我的物理以及数学功底太差,放弃使用物理公式后,我就直接在发射和爆炸期间快速移动,然后达到爆炸时间点后自然下落,由于轻物会受到浮力作用,所以此时下落不能使用自由落体,便采用简单位移

至此,我已正式学软件3年,写代码约2w+行,涵盖C/C++、C#、Python、Java、PHP、Shell、JavaScript,代码量虽然不多,但每一次写代码,我都如同设计一件艺术品一样,只有这样,才能在每一次的代码中学习,也正是因为喜欢

在我前不久刚辞掉的工作中,我的领导问我为什么要走,我回答因为加班
生活和工作平衡,这也是最近GitHub上很火的一个抵制加班的项目宣言,有效的工作、充实美好的生活这样子,正是我所追求的
我不想每天起早上班,然后加班到晚上,回家累的葛优躺,连周末的好心态也被搞的躺床上不想动
我想工作有工作的需求代码,回家也有自己的想法去写代码,然后练练吉他、画会画,学些有意思的东西
我们并不是廉价劳动力,也不要做廉价劳动力,我们有自己的生活
最后领导给我说,只有你在工作时写代码,有甲方给提需求,这样代码才能更好,这样才有意义
我没怎么回答,如果连自己都做不了,哪能去漫天星河
最终还是辞掉了工作,尽管我非常喜欢这个团队氛围,我领导技术也非常厉害,但是我更喜欢生活

遗留问题

  1. 由于使用requestAnimationFrame()函数实现动画,所以会存在性能问题
    比如突然切换进窗口的同时发射了礼花,礼花就会聚集,原因不详
    比如第一炮礼花未完全消失的同时发射第二炮,会导致第二炮礼花距离更远,原因不详
    但是当窗口稳定、礼花已完全落下时,比较稳定
    以上两个问题并无特别大的影响
    不过会有一个有趣的现象,短时间内连续发射礼花,会导致后面的礼花越来越远,以至于越过视窗
    仔细排查代码无任何问题,最终把问题定位到时间点上,因为通过反向推测,只能是时间点的问题,但我甚至核查每个礼花碎片的时间点也没有异常,所以我暂时把问题归咎到这个函数上了,等有机会深入了解此函数再继续排查

结束

GitHub主页
GitHub项目地址

[JavaScript][微信礼花][算法]JavaScript实现类似微信礼花算法(已实现封装)相关推荐

  1. js打乱数组的顺序_如何用 js 实现一个类似微信红包的随机算法

    如何用 js 实现一个类似微信红包的随机算法 js, 微信红包, 随机算法 "use strict"; /** * * @author xgqfrms * @license MIT ...

  2. JavaScript:判断当前浏览器是否为微信浏览器

    <script type="text/javascript"> function CheckWeChatBrowser() {var ua = navigator.us ...

  3. html判断是否在微信里打开,JavaScript判断浏览器内核,微信打开自动提示在浏览器打开...

    微信会屏蔽 URL 自定义的 scheme ,导致无法跳转手机中的浏览器.网上有一些工具类网站可以实现直接跳转浏览器,之后有机会我会整理一下.我们今天只讨论通过 JavaScript 判断是否在微信浏 ...

  4. 腾讯地图微信小程序JavaScript SDK

    简介 腾讯位置服务为微信小程序提供了基础的标点能力.线和圆的绘制接口等地图组件和位置展示.地图选点等地图API位置服务能力支持,使得开发者可以自由地实现自己的微信小程序产品. 在此基础上,腾讯位置服务 ...

  5. 类似微信群聊九宫格头像的算法实现

    在工作中遇到了一个开发的需求是将多选的图片聚合起来,类似于微信群聊那种九宫格的头像的那种.当然遇到这个需求首先肯定会从网上查找一些资料,发现大部分的实现类似于通过定义九宫格的ImageView控件来实 ...

  6. 利用html5实现类似微信的手机摇一摇功能

    利用html5实现类似微信的手机摇一摇功能,并播放音乐. 1.  deviceOrientation:封装了方向传感器数据的事件,可以获取手机静止状态下的方向数据,例如手机所处角度.方位.朝向等. 2 ...

  7. 101道算法javaScript描述【一】

    文章目录 小册介绍 你会收获到什么? 适宜人群 你需要准备什么 学习指南 高效地学习 一起变得更好 最后 开篇--复杂度 时间复杂度 常见的时间复杂度 递归的时间复杂度 空间复杂度 常见的空间复杂度 ...

  8. Android计步模块(类似微信运动)

    最近在项目中研究计步模块,每天0点开始记录当天的步数,类似微信运动.碰到了不少坑今天有时间整理出来给大家看看. 做之前在google.baidu.github上搜了个遍没找到好的,大多数都是需要在后台 ...

  9. 微信红包业务,为什么采用轮询算法?

    目录 前言 基本的负载算法 平滑加权轮询算法 一致性哈希算法 最小活跃数算法 最优响应算法 总结 前言 负载均衡这个概念,几乎在所有支持高可用的技术栈中都存在,例如微服务.分库分表.各大中间件(MQ. ...

最新文章

  1. JDK 5.0 注解的使用
  2. CSS3的学习--实现瀑布流
  3. 13.MapReduce第3部分(编程实践WordCount)
  4. hdu1198 Farm Irrigation —— dfs or 并查集
  5. Java黑皮书课后题第10章:**10.23(实现String类)在Java库中提供了String类,给出你自己对下面方法的实现(将新类命名为MyString2)
  6. 计算机一级应用于段落还是文字,计算机一级复习资料
  7. mysql 分组查出来横向展示_MySQL汇总分析(group by)
  8. 计算机行业深度分析,广发证券计算机行业深度分析
  9. 中国电信5G技术承载网络
  10. 检测mysql表更新吗,知网查重系统的数据库是多久更新一次?
  11. Guava的Splitter和Joiner
  12. Ubuntu20.4:安装OpenCV4,配置vscode+CMake作为基本开发环境
  13. P2P追债也用上大数据
  14. 灰度图片及彩色图片像素点统计及显示
  15. STM32 定时器的简单应用 1ms中断代码
  16. aardio - 小窍门及注意事项收集贴
  17. Quartus Prime 19.1 下载教程
  18. 人工智能技术与物联网的融合
  19. 模拟量使用计算机电缆,远东电缆关于计算机电缆选型应用的友情提醒
  20. 初中科学计算机使用,350MS 82MS科学计算器使用方法(初中).doc

热门文章

  1. python爬虫——request模块讲解
  2. android下载zip到assets,Android将assets中的zip压缩文件解压到SD卡
  3. mysql安装详细步骤图解:
  4. 深度学习-数据增强与扩充
  5. Android keystore 密码找回
  6. ClickHouse系列1-CK介绍
  7. logit回归模型假设_一文让你搞懂Logistic回归模型
  8. 基于Vue和Element-ui组件库搭建的后台管理系统
  9. SENet与eSE模块
  10. 电源IC大致分为线性稳压器和开关稳压器两种!