先来看看最终的效果:

GitHub项目: CyandevToys / ParticleWeb
是不是还蛮酷的呢?本文我们就来一点一点分析怎么实现它!

分析

首先我们看看这个效果具体有那些要点。首先,这么炫酷的效果肯定是要用到 Canvas 了,每个星星可以看作为一个粒子,因此,整个效果实际上就是粒子系统了。此外,我们可以发现每个粒子之间是相互连接的,只不过离的近的粒子之间的连线较粗且透明度较低,而离的远的则相反。

开始 Coding

HTML部分

这部分我就简单放了一个 canvas 标签,设置样式使其填充全屏。

<canvas height= "620" width= "1360" id= "canvas" style= "position: absolute; height: 100%;" ></canvas>

然后为了让所有元素没有间距和内补,我还加了一条全局样式:

<style>
     * {
       margin : 0 ;
       padding : 0 ;
     }
</style>

JavaScript 部分

下面我们来写核心的代码。首先我们要得到那个 canvas 并得到绘制上下文:

var canvasEl = document.getElementById( 'canvas' );
var ctx = canvasEl.getContext( '2d' );
var mousePos = [0, 0];

紧接着我们声明两个变量,分别用于存储“星星”和边:

var nodes = [];
var edges = [];

下一步,我们做些准备工作,就是让画布在窗口大小发生变化时重新绘制,并且调整自身分辨率:

window.onresize = function () {
     canvasEl.width = document.body.clientWidth;
     canvasEl.height = canvasEl.clientHeight;
     if (nodes.length == 0) {
       constructNodes();
     }
     render();
};
window.onresize(); // trigger the event manually.

我们在第一次修改大小后构建了所有节点,这里就要用到下一个函数(constructNodes)了

这个函数中我们随机创建几个点,我们用字典对象的方式存储这些点的各个信息:

function constructNodes() {
     for ( var i = 0; i < 100; i++) {
       var node = {
         drivenByMouse: i == 0,
         x: Math.random() * canvasEl.width,
         y: Math.random() * canvasEl.height,
         vx: Math.random() * 1 - 0.5,
         vy: Math.random() * 1 - 0.5,
         radius: Math.random() > 0.9 ? 3 + Math.random() * 3 : 1 + Math.random() * 3
       };
       nodes.push(node);
     }
     nodes.forEach( function (e) {
       nodes.forEach( function (e2) {
         if (e == e2) {
           return ;
         }
         var edge = {
           from: e,
           to: e2
         }
         addEdge(edge);
       });
     });
   }

为了实现后面一个更炫酷的效果,我给第一个点加了一个 drivenByMouse 属性,这个点的位置不会被粒子系统管理,也不会绘制出来,但是它会与其他点连线,这样就实现了鼠标跟随的效果了。

这里稍微解释一下 radius 属性的取值,我希望让绝大部分点都是小半径的,而极少数的点半径比较大,所以我这里用了一点小 tricky,就是用概率控制点的半径取值,不断调整这个概率阈值就能获取期待的半径随机分布。

点都构建完毕了,就要构建点与点之间的连线了,我们用到双重遍历,把两个点捆绑成一组,放到 edges 数组中。注意这里我用了另外一个函数来完成这件事,而没有直接用 edges.push() ,为什么?

假设我们之前连接了 A、B两点,也就是外侧循环是A,内侧循环是B,那么在下一次循环中,外侧为B,内侧为A,是不是也会创建一条边呢?而实际上,这两个边除了方向不一样以外是完全一样的,这完全没有必要而且占用资源。因此我们在 addEdge 函数中进行一个判断:

function addEdge(edge) {
     var ignore = false ;
     edges.forEach( function (e) {
       if (e.from == edge.from && e.to == edge.to) {
         ignore = true ;
       }
       if (e.to == edge.from && e.from == edge.to) {
         ignore = true ;
       }
     });
     if (!ignore) {
       edges.push(edge);
     }
   }

至此,我们的准备工作就完毕了,下面我们要让点动起来:

function step() {
     nodes.forEach( function (e) {
       if (e.drivenByMouse) {
         return ;
       }
       e.x += e.vx;
       e.y += e.vy;
       function clamp(min, max, value) {
         if (value > max) {
           return max;
         } else if (value < min) {
           return min;
         } else {
           return value;
         }
       }
       if (e.x <= 0 || e.x >= canvasEl.width) {
         e.vx *= -1;
         e.x = clamp(0, canvasEl.width, e.x)
       }
       if (e.y <= 0 || e.y >= canvasEl.height) {
         e.vy *= -1;
         e.y = clamp(0, canvasEl.height, e.y)
       }
     });
     adjustNodeDrivenByMouse();
     render();
     window.requestAnimationFrame(step);
   }
   function adjustNodeDrivenByMouse() {
     nodes[0].x += (mousePos[0] - nodes[0].x) / easingFactor;
     nodes[0].y += (mousePos[1] - nodes[0].y) / easingFactor;
   }

看到这么一大段代码不要害怕,其实做的事情很简单。这是粒子系统的核心,就是遍历粒子,并且更新其状态。更新的公式就是

v = v + a
s = s + v

a是加速度,v是速度,s是位移。由于我们这里不涉及加速度,所以就不写了。然后我们需要作一个边缘的碰撞检测,不然我们的“星星”都无拘无束地一点点飞~走~了~。边缘碰撞后的处理方式就是让速度矢量反转,这样粒子就会“掉头”回来。

还记得我们需要做的鼠标跟随吗?也在这处理,我们让第一个点的位置一点一点移动到鼠标的位置,下面这个公式很有意思,可以轻松实现缓动:

x = x + (t - x) / factor

其中 factor 是缓动因子,t 是最终位置,x 是当前位置。至于这个公式的解释还有个交互大神 Bret Victor 在他的演讲中提到过,视频做的非常好,有条(ti)件(zi)大家一定要看看:

Bret Victor – Stop Drawing Dead Fish

好了,回到主题。我们在上面的函数中处理完了一帧中的数据,我们要让整个粒子系统连续地运转起来就需要一个timer了,但是十分不提倡大家使用setInterval,而是尽可能使用requestAnimationFrame,它能保证你的帧率锁定在 <= 60 fps ,不至于爆掉你的浏览器。函数参数即为回调函数,看起来有点像递归调用,但实际上,浏览器内部处理是将这个回调函数先入事件列队,等所有绘制计算工作完毕后再从列队中取出执行,十分高效。 剩下的就是绘制啦:

function render() {
     ctx.fillStyle = backgroundColor;
     ctx.fillRect(0, 0, canvasEl.width, canvasEl.height);
     edges.forEach( function (e) {
       var l = lengthOfEdge(e);
       var threshold = canvasEl.width / 8;
       if (l > threshold) {
         return ;
       }
       ctx.strokeStyle = edgeColor;
       ctx.lineWidth = (1.0 – l / threshold) * 2.5;
       ctx.globalAlpha = 1.0 – l / threshold;
       ctx.beginPath();
       ctx.moveTo(e.from.x, e.from.y);
       ctx.lineTo(e.to.x, e.to.y);
       ctx.stroke();
     });
     ctx.globalAlpha = 1.0;
     nodes.forEach( function (e) {
       if (e.drivenByMouse) {
         return ;
       }
       ctx.fillStyle = nodeColor;
       ctx.beginPath();
       ctx.arc(e.x, e.y, e.radius, 0, 2 * Math.PI);
       ctx.fill();
     });
   }

常规的 Canvas 绘图操作,注意beginPath 一定要调用,不然你的线就全部穿在一起了… 需要说明的是,在绘制边的时候,我们先要计算两点距离,然后根据一个阈值来判断是否要绘制这条边,这样我们才能实现距离远的点之间连线不可见的效果。

到这里,我们的整个效果就完成了。如果不明白大家也可以去我文章开头放的 repo 离去看完整的源码。Have fun!!

原文地址:http://www.jianshu.com/p/f5c0f9c4bc39#

用 Canvas 编织璀璨星空图相关推荐

  1. 前端教程:用 Canvas 编织璀璨星空图

    是不是还蛮酷的呢?利用周末时间我们来学习并实现一下,本文我们就来一点一点分析怎么实现它! 分析 首先我们看看这个效果具体有哪些要点.首先,这么炫酷的效果肯定是要用到 Canvas 了,每个星星可以看作 ...

  2. 涌html编写星空图,canvas实现十二星座星空图

    效果如下: 代码如下: canvas星座 * { margin: 0; padding: 0; } #box{ margin:10px 0 0 10px;; } input{ outline: non ...

  3. ☆超简单css两张静态图片制作动态星空图(无需js和canvas,附所有代码复制即可用)

    css两张静态图片制作动态星空图 上个效果图,gif图像质量问题所以显示月亮蓝边,实际上很好看 需要素材: star.html代码如下 <html lang="en"> ...

  4. canvas绘制星空底图

    canvas的初始化就没写了,仅写地图实现过程,长宽均为420 //星空图底图function drawBaseSky(tempcxt){tempcxt.clearRect(0,0,420,420); ...

  5. Android GNSS 可视卫星星空图/卫星天顶图 原理及画法介绍

    目录 1 获取卫星信息 2 自定义视图 3 绘制底图 3.1 圆的画法 3.2 直线的画法 3.3 绘制刻度 3.4 绘制结果 4 绘制卫星 4.1 计算卫星坐标 4.2 获取卫星旗帜 4.3 绘制卫 ...

  6. 用我的沃土编织你的穹顶:华为的欧洲告白

    技术的跨文明启迪,经常被誉为人类最美丽的乐章之一. 15世纪,布鲁内莱斯基在主持修建圣母百花大教堂时,用了大量从阿拉伯传来的数学公式:而这些数学知识得以在欧洲流传,又要大量归功于来自中国的纸张和造纸术 ...

  7. 『 canvas 特效』一文教你绘制绚丽的星空背景 TS + ES6

    介绍 很久没有写关于 canvas 效果的文章了,刚好最近又学到了一个新的特效,使用 canvas 绘制多层次动态星空背景,今天就分享给大家.首先我们依旧来看一下最终实现的效果,如图所示: 由于录制 ...

  8. canvas绘制星座(黄道十二宫)

    canvas绘制黄道十二宫星座 效果图 对照图 准备工作 开始撸代码 白嫖作者的代码 效果图 对照图 准备工作 (以下所有片段代码为手敲,难免会有语法错误,请不要复制,文末会发布全部代码) 先准备一张 ...

  9. 我的星座图 php,canvas 绘制星座图(好玩)--转载

    canvas星座 * { margin: 0; padding: 0; } #box{ margin:10px 0 0 10px;; } input{ outline: none; font-size ...

最新文章

  1. any() missing 1 required positional arguments: dim
  2. Protocol Buffer Java应用实例
  3. php 数据库查询乱码,怎么解决php数据库查询乱码问题
  4. Spring MVC配置多个视图解析器(FreeMarker,JSP)
  5. 2020 中国开源年会(COSCon'20)再启程:开源向善(Open Source for Good)
  6. Android程序结构
  7. mysql 将 字符 转换成 数字
  8. oracle如何修改initial参数,oracle初始化参数设置
  9. 宏基4750网卡驱动linux,宏基4750g驱动下载-宏基4750g网卡驱动程序官方版 - 极光下载站...
  10. 机器学习(一)线性模型之岭回归器RidgeRegressor
  11. MISRA C_2012规则翻译、解读、示例
  12. Java微信如何自动添加好友,微信自动加好友 模拟位置
  13. 把N*N矩阵顺时针旋转90°输出(2018携程校招笔试题)
  14. String类中getBytes()方法的使用
  15. 使用奥维地图加载星图地球数据云地图数据
  16. 基于Python的线性回归预测模型介绍及实践
  17. C++实现生产者消费者队列
  18. [Python] 错误“IndentationError: unindent does not match any outer indentation level”是什么意思?...
  19. 逆水寒能不能网页预约服务器,逆水寒春暖花开服务器怎么预约?春暖花开服务器预约方法介绍...
  20. 存储在icloud云盘文件夹顶层_iCloud云盘文件夹共享功能使用方法

热门文章

  1. 如何取消锁定计算机,如何取消解除计算机锁定窗口
  2. oracle查看外键约束哪个字段,oracle 查看主外键约束(转)
  3. matlab并行fft实现,matlab fft实现相关
  4. J2mePolish 打包帮助
  5. 第一个驱动程序之i2c驱动架构介绍
  6. WiMAX的切换实现
  7. JavaScript调用OCX控件,运行时报错:对象不支持“XXX”属性或方法【已解决】
  8. chkconfig安装
  9. 微软宣布Windows 10 21H1版本,将于5月10日推送
  10. 使用python判断同名文件