炫酷粒子表白,双十一脱单靠它了!
双十一光棍节又要来临了,每年这个时候都是本人最苦闷的时刻。日渐消瘦的钱包,愈发干涸的双手,虽然变强了,头却变凉了。今年一定要搞点事情!
<img src="https://img.alicdn.com/tfs/TB...; alt="fxxking things" width="160">
最近听女神说想谈恋爱了,✧(≖ ◡ ≖) 嘿嘿,一定不能放过这个机会,给她来个不一样的表白。
<img src="https://img.alicdn.com/tfs/TB...; alt="我老婆" width="360">
作为整天搞可视化的前端攻城狮,最先想到的就是常玩的各种粒子。那么咱们就一起来把这个粒子系统玩出花来吧。
演示地址
用粒子组成文字
首先,咱们想下要如何将一系列的粒子组成一句表白呢?
实现原理其实很简单,Canvas 中有个 getImageData 的方法,可以得到一个矩形范围所有像素点数据。那么我们就试试来获取一个文字的形状吧。
第一步,用 measureText 的方法来计算出文字适当的尺寸和位置。
// 创建一个跟画布等比例的 canvas
const width = 100;
const height = ~~(width * this.height / this.width); // this.width , this.height 说整个画布的尺寸
const offscreenCanvas = document.createElement('canvas');
const offscreenCanvasCtx = offscreenCanvas.getContext('2d');
offscreenCanvas.setAttribute('width', width);
offscreenCanvas.setAttribute('height', height);// 在这离屏 canvas 中将我们想要的文字 textAll 绘制出来后,再计算它合适的尺寸
offscreenCanvasCtx.fillStyle = '#000';
offscreenCanvasCtx.font = 'bold 10px Arial';
const measure = offscreenCanvasCtx.measureText(textAll); // 测量文字,用来获取宽度
const size = 0.8;
// 宽高分别达到屏幕0.8时的size
const fSize = Math.min(height * size * 10 / lineHeight, width * size * 10 / measure.width); // 10像素字体行高 lineHeight=7 magic
offscreenCanvasCtx.font = `bold ${fSize}px Arial`;// 根据计算后的字体大小,在将文字摆放到适合的位置,文字的坐标起始位置在左下方
const measureResize = offscreenCanvasCtx.measureText(textAll);
// 文字起始位置在左下方
let left = (width - measureResize.width) / 2;
const bottom = (height + fSize / 10 * lineHeight) / 2;
offscreenCanvasCtx.fillText(textAll, left, bottom);
咱们可以 appendChild 到 body 里看眼
好的。同学们注意,我要开始变形了 [推眼镜] 。
getImageData 获取的像素数据是一个 Uint8ClampedArray (值是 0 - 255 的数组),4 个数一组分别对应一个像素点的 R G B A 值。我们只需要判断 i * 4 + 3 不为 0 就可以得到需要的字体形状数据了。
// texts 所有的单词分别获取 data ,上文的 textAll 是 texts 加一起
Object.values(texts).forEach(item => {offscreenCanvasCtx.clearRect(0, 0, width, height);offscreenCanvasCtx.fillText(item.text, left, bottom);left += offscreenCanvasCtx.measureText(item.text).width;const data = offscreenCanvasCtx.getImageData(0, 0, width, height);const points = [];// 判断第 i * 4 + 3 位是否为0,获得相对的 x,y 坐标(使用时需乘画布的实际长宽, y 坐标也需要取反向)for (let i = 0, max = data.width * data.height; i < max; i++) {if (data.data[i * 4 + 3]) {points.push({x: (i % data.width) / data.width,y: (i / data.width) / data.height});}}// 保存到一个对象,用于后面的绘制geometry.push({color: item.hsla,points});
})
制定场景,绘制图形
文字图形的获取方式以及搞定了,那么咱们就可以把内容整体输出了。咱们定义一个简单的脚本格式。
// hsla 格式方便以后做色彩变化的扩展
const color1 = {h:197,s:'100%',l:'50%',a:'80%'};
const color2 = {h:197,s:'100%',l:'50%',a:'80%'};
// lifeTime 祯数
const Actions = [{lifeTime:60,text:[{text:3,hsla:color1}]},{lifeTime:60,text:[{text:2,hsla:color1}]},{lifeTime:60,text:[{text:1,hsla:color1}]},{lifeTime:120,text:[{text:'I',hsla:color1},{text:'❤️',hsla:color2},{text:'Y',hsla:color1},{text:'O',hsla:color1},{text:'U',hsla:color1}]},
];
根据预设的脚本解析出每个场景的图形,加一个 tick 判断是否到了 lifeTime 切换到下一个图形重新绘制图形。
function draw() {this.tick++;if (this.tick >= this.actions[this.actionIndex].lifeTime) {this.nextAction();}this.clear();this.renderParticles(); // 绘制点this.raf = requestAnimationFrame(this.draw);
}function nextAction() {....//切换场景 balabala..this.setParticle(); // 随机将点设置到之前得到的 action.geometry.points 上
}
这样咱们基本的功能已经完成了。
能不能再给力一点
说好的粒子系统,现在只是 context.arc 简单的画了一点。那咱们就来加个粒子系统吧。
class PARTICLE {// x,y,z 为当前的坐标,vx,vy,vz 则是3个方向的速度constructor(center) {this.center = center;this.x = 0;this.y = 0;this.z = 0;this.vx = 0;this.vy = 0;this.vz = 0;}// 设置这些粒子需要运动到的终点(下一个位置)setAxis(axis) {this.nextX = axis.x;this.nextY = axis.y;this.nextZ = axis.z;this.color = axis.color;}step() {// 弹力模型 距离目标越远速度越快this.vx += (this.nextX - this.x) * SPRING;this.vy += (this.nextY - this.y) * SPRING;this.vz += (this.nextZ - this.z) * SPRING;// 摩擦系数 让粒子可以趋向稳定this.vx *= FRICTION;this.vy *= FRICTION;this.vz *= FRICTION;this.x += this.vx;this.y += this.vy;this.z += this.vz;}getAxis2D() {this.step();// 3D 坐标下的 2D 偏移,暂且只考虑位置,不考虑大小变化const scale = FOCUS_POSITION / (FOCUS_POSITION + this.z);return {x: this.center.x + (this.x * scale),y: this.center.y - (this.y * scale),};}
}
大功告成!
既然是 3D 的粒子,其实这上面还有不是文章可做,同学们可以发挥想象力来点更酷炫的。
还有什么好玩的
上面是将粒子摆成文字。那咱们当然也可以直接写公式摆出个造型。
// Actions 中用 func 代替 texts
{lifeTime: 100,func: (radius) => {const i = Math.random() * 1200;let x = (i - 1200 / 2) / 300;let y = Math.sqrt(Math.abs(x)) - Math.sqrt(Math.cos(x)) * Math.cos(30 * x);return {x: x * radius / 2,y: y * radius / 2,z: ~~(Math.random() * 30),color: color3};}
}
再把刚才文字转换形状的方法用一下
{lifeTime: Infinity,func: (width, height) => {if(!points.length){const img = document.getElementById("tulip");const offscreenCanvas = document.createElement('canvas');const offscreenCanvasCtx = offscreenCanvas.getContext('2d');const imgWidth = 200;const imgHeight = 200;offscreenCanvas.setAttribute('width', imgWidth);offscreenCanvas.setAttribute('height', imgHeight);offscreenCanvasCtx.drawImage(img, 0, 0, imgWidth, imgHeight);let imgData = offscreenCanvasCtx.getImageData(0, 0, imgWidth, imgHeight);for (let i = 0, max = imgData.width * imgData.height; i < max; i++) {if (imgData.data[i * 4 + 3]) {points.push({x: (i % imgData.width) / imgData.width,y: (i / imgData.width) / imgData.height});}}}const p = points[~~(Math.random() * points.length)]const radius = Math.min(width * 0.8, height * 0.8);return {x: p.x * radius - radius / 2,y: (1 - p.y) * radius - radius / 2,z: ~~(Math.random() * 30),color: color3};}
}
完美 ?。
然后咱也可以用 drawImage 绘制图片来代替 arc 画点。
<img src="https://img.alicdn.com/tfs/TB...; width="160">
等等!!前面的效果总觉得哪里不对劲,好像有些卡 。
优化小提示
- 分层。如果还需要增加一些其他的内容到 Canvas 中的话,可以考虑拆出多个 Canvas 来做。
- 减少属性设置。包括 lineWidth、fillStyle 等等。Canvas 上下文是很复杂的一个对象,当你调它的一些属性设置时消耗的性能还是不少的。arc 之类的画图方法也要减少。
- 离屏绘制。不用 arc 画点,要怎么办。上面的延迟其实就是每次画点时都调用了一遍 fillStyle、arc。但是当我们绘制图片 drawImage 时,性能明显会好上很多。drawImage 除了直接绘图片外,还能绘制另一个 Canvas,所以我们提前将这些点画到一个不在屏幕上的 Canvas 里就可以了。
- 减少 js 计算,避免堵塞进程,可以使用 web worker。当然咱们目前的计算完全用不上这个。
我这使用了 3000 个粒子,对比下使用离屏绘制前后的帧率。
总结
现在唯一限制你的就是想象力了。大家一起去征服老板,征服女神!
这个双十一脱贫脱单不脱发!
好了不说了,女神喊我修电脑去了。
参考
- deformable particles
- heart.png
- Canvas 参考手册
- y=sqrt(abs(x))-sqrt(cos(x))*cos(40x)
文章可随意转载,但请保留此 原文链接。
非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。
炫酷粒子表白,双十一脱单靠它了!相关推荐
- 使用vue3+element-plus+particles实现炫酷(粒子背景)登陆界面
效果图如下: 撸代码前准备(从无到有): 1.导入element-plus: # yarn yarn add element-plus # npm npm install element-plus - ...
- H5炫酷特效系列4——炫酷粒子变化特效
这个示例虽然没有相对比较实用的特性,但是表现出来的效果确实非常震撼眼球.先上效果图,让各位同胞感受一下: 特效的表现感很强,有兴趣的读者可以仔细学习一下下面的代码,尝试着理解其中的逻辑. <!D ...
- 【HTML——炫酷粒子】(效果+代码)
效果 代码 下面即为全部源代码喔~! HTML--炫酷粒子.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional/ ...
- 153.炫酷粒子背景特效
效果 (源码网盘地址在最后) 最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了. github 地址:https://github.co ...
- Flutter自定义Widget实例 -如何创建炫酷粒子时钟效果!
周末发表了一篇文章<这个项目也太屌了吧>,给大家推荐了一个炫酷的Flutter粒子时钟项目,不过没有将具体实现思路和代码,所幸,作者自己写了一篇博客将这个项目的背景.实现思路.和所遇到的问 ...
- Flutter自定义Widget之炫酷粒子时钟效果
周末发表了一篇文章<这个项目也太屌了吧>,给大家推荐了一个炫酷的Flutter粒子时钟项目,不过没有将具体实现思路和代码,所幸,作者自己写了一篇博客将这个项目的背景.实现思路.和所遇到的问 ...
- 双十一脱单进行时,珍爱网掀起“云相亲”浪潮
11月9日至11月11日,四川省共青团泸县县委携手珍爱网公益项目部举办"缘"来是你 "双十一"脱单公益相亲会,在"光棍节"来临之际,助力当地 ...
- 马上跨年了,室友靠我的十款不同表白代码顺利脱单【内附源码】
你的专属表白神器 一.故事背景 二.十款表白神器展示 1.爱心树 2.代码表白式+爱心 3.小文章+爱心 4.死皮赖脸式(推荐使用) 5.旋转照片墙 6.千纸鹤 7.文字爱心 8.烟花+蛋糕 9.红玫 ...
- 炫酷车网双十一大动作,宇宙汽车综合服务第一网红横空出世
炫酷车网,总是与众不同 作为宇宙第一网红的炫酷车网,从来不走寻常路 当别人在送红包.送内饰的时候 炫酷车网已经创造了汽车销售.服务.售后等不可不说的神话 当别人在为双十一大搞噱头,故弄玄虚的时候 炫酷 ...
最新文章
- HTML在网页设计中是什么作用?
- 使用Networkx进行图的相关计算——黑产集团挖掘,我靠,可以做dns ddos慢速攻击检测啊...
- oracle之4多行函数之分组函数
- 我思故我在之编程规范及编程思想篇
- 读《不要告诉我你懂margin(海玉的博客)》有感
- graphpad7.04多组比较p值_同是折线图为何你却这么优秀,这才是多组数据作图应该有的样子...
- java中的继承(一)
- volatile和synchronized关键字
- 流式计算之Storm简介
- 2019年上海理工大学《高等代数》试题和答案——解题人(蔡宇)
- Microsoft Dynamics NAV成本原理讲解
- 软件工程师的衰落与程序员的崛起
- keil5生成bin文件和axf文件
- Cesium 地形图
- DialogFragment设置不可取消
- Python网络爬虫数据采集实战(八):Scrapy框架爬取QQ音乐存入MongoDB
- eclipse 找不到或无法加载主类(已解决)
- ❤520情人节送女朋友的生日礼物~html+css+js实现抖音炫酷樱花3D相册(含音乐)
- MySQL细节篇02_modify和change的区别
- 配电室环境远程监控物联网方案
热门文章
- 18966 两两配对差值最小
- 在Microsoft Office、Visio、WPS中用LaTeX的方式编辑公式
- SpringBoot application.properties读取属性配置文件中文显示为乱码问题的解决
- MySQL高级篇知识点——MySQL 事务日志
- 2022_WWW_Improving Graph Collaborative Filtering with Neighborhood-enriched Contrastive Learning
- 秦曾昌人工智能课程---2、机器学习中的数学基础2
- 网络中搜不到局域网内的其他计算机,局域网中搜不到其他计算机怎么修复
- EDM数据之大数据是什么
- 【2021-MOOC-浙江大学-陈越、何钦铭-数据结构】树-中
- 查询1990年出生的学生名单