如何基于Canvas来模拟真实雨景Part1:预备知识和创建基本对象
我们之前使用CSS3实现过逼真的雨滴,但这个方法只适合用来模拟一些静态场景或少量雨滴动画,如果用来实现大量雨滴的动画,会遭遇严重的性能问题。而使用Canvas能利用硬件加速,渲染性能要高得多,适合用来创建大量粒子的动画。本例我们讲解如何使用Canvas 2D来实现一个“雨打窗户”的动画场景,雨滴(粒子)的数量可以有成百上千乃至上万个。本例不涉及更为复杂的3D空间场景的模拟。
预备知识
如果你不知道怎么使用Canvas 2D,请先阅读踏得网在线教程Canvas基础知识。
基本上浏览器为我们准备了一个2D渲染上下文,我们在这里绘制图形图像对象,绘制好后再和页面合成。所以和CSS不同,Canvas内部对象的变化不会影响DOM文档,也因此不会导致DOM重排和重绘而影响性能。
除了Canvas接口外,我们还需要具备一些基本的物理学和数学知识。我们观察实际物理世界中的雨点,还有这么几个特征:
能反射外部环境景色(真实的反射、环境折射情况相当复杂)
小的雨点停留在窗户上,由于有阻力而静止不动,但会被移动的雨滴所吞没
大到一定程度的雨点会因为重力效应而滑落
雨滴在滑落的过程中形成轨迹,这个现象会导致雨滴变小
由于雨点是液体,足够接近的雨点会相互吸引并融合成单个更大的雨点(因为单个球面积要远小于2个单独的球面积,因此融合后液体将处于势能最小状态即最稳定状态。)
如果存在风向,那么雨滴将是斜着掉落/滑落的。
我们的代码不会去试图完美模拟这些特征,因为在一个快速变换的环境中人眼留意的是总体印象和感觉。
我们将去除一些不必要的复杂性,比如暂不考虑环境折射,另外我们把雨滴降低维度在2D空间上,简化为一个运动的小圆点(在三维空间是一个椭球体),它包含大小、位置、速度、加速度属性,这样在计算雨点距离时只需要考虑x、y轴分量,而求体积也退化为求面积。
创建雨景容器
我们首先创建一个场景视图对象来容纳背景(远景图片)、玻璃窗户(我们的观察点)和雨滴:
function RainyView(options, canvas) { this.img = options.image;//背景图片 this.options = options;//参数 this.drops = [];//一组雨滴 // 创建canvas容器,用来绘制对象和动画 this.canvas = canvas || this.prepareCanvas(); this.prepareBackground();//创建背景 this.prepareGlass();//创建窗户 // 设置window.requestAnimFrame动画方法 this.setRequestAnimFrame(); }
其中setRequestAnimFrame方法实现了requestAnimFrame接口,这个是比setTimeout更好的动画绘制接口,在Canvas按钮绕边动画一文中已有详细说明。
创建背景
RainyView.prototype.prepareBackground = function() { this.background = document.createElement('canvas'); this.background.width = this.canvas.width; this.background.height = this.canvas.height; var context = this.background.getContext('2d'); context.clearRect(0, 0, this.canvas.width, this.canvas.height); context.drawImage(this.img, this.options.crop[0], this.options.crop[1], this.options.crop[2], this.options.crop[3], 0, 0, this.canvas.width, this.canvas.height); //blur the background ... };
上面的代码创建一个background的canvas,然后把图像绘制在上面,由于是远景图并且是透过玻璃看出去的,我们需要给图片添加模糊效果,这个有很多方法,比如CSS3的blur、scale方法,以及使用JS的一些基于像素处理的模糊算法,比如相邻像素的混合算法、高斯模糊、栈模糊算法。这不是本文的重点,不做进一步细述。
创建窗户
RainyView.prototype.prepareGlass = function() { this.glass = document.createElement('canvas'); this.glass.width = this.canvas.width; this.glass.height = this.canvas.height; this.context = this.glass.getContext('2d'); };
这个比较简单,使用一个新的canvas上下文来绘制窗户。
创建雨滴
/** * 定义一个雨滴对象 * @param RainyView 雨滴的父容器 * @param centerX 雨滴中心的x坐标 * @param centerY 雨滴中心的y坐标 * @param min 最小尺寸 * @param base 随机尺寸基准 */ function Drop(RainyView, centerX, centerY, min, base) { this.x = Math.floor(centerX); this.y = Math.floor(centerY); this.r = (Math.random() * base) + min; this.RainyView = RainyView; this.context = RainyView.context; this.reflection = RainyView.reflected; }
你可以看到雨滴的大小是随机生成的,介于min和min+base之间。
雨滴对象除了上述的属性外,还有两个核心的方法:绘制和移动(滑落)。
Drop.prototype.draw = function() { this.context.save();//保存画板绘图状态 this.context.beginPath(); this.context.arc(this.x, this.y, this.r, 0, Math.PI * 2, true); this.context.closePath(); if (this.RainyView.reflection) { this.RainyView.reflection(this); } this.context.restore();//恢复画板绘图状态 }; Drop.prototype.move = function() { if (this.terminate) { return false; } var stopped = this.RainyView.gravity(this); if (!stopped && this.RainyView.trail) { this.RainyView.trail(this); } if (this.RainyView.options.enableCollisions) { var collisions = this.RainyView.matrix.update(this, stopped); if (collisions) { this.RainyView.collision(this, collisions); } } return !stopped || this.terminate; };
上述draw方法就是绘制一个半径为r的圆,为了简单起见,暂未考虑当两个Drop对象由于接近而融合时所产生的变形。move方法完成两个任务,一个是重力掉落(gravity函数),另外一个功能是碰撞检测(当发生碰撞时,触发融合)。由于这两个方法和环境有关,所以这里实现为其容器RainyView的方法。
到此,我们就完成了雨景布局和基本对象构建,接下去就是要为对象添加实际的动画。
我们在第2部分进行详细描述。
- 相关文章
html5跨平台实战-第一周-水平测验-新闻列表页面
这是一个DIV+CSS布局页面的一个实例,主要介绍POSITION定位、导航UL LI的制作、利用浮动原理对页面分栏、分列的页面布局。新闻页面的效果图
WebGL、Asm.js和WebAssembly概念简介
随着HTML技术的发展,网页要解决的问题已经远不止是简单的文本信息,而包括了更多的高性能图像处理和3D渲染方面。这正是要引入WebGL、Asm.js和WebAssembly这些技...
CSS3特性查询(Feature Query: @supports)功能简介
这是2017年不能不了解和学习的一个CSS新特性,非常实用,考虑到现实世界浏览器的复杂性,该特性本应该先于其他新特性出来。我们已经知道使用媒体查询(Media Que...
CSS3人行走动作图解和动画实现
对于人类而言,行走是一种很自然的想要前进并防止跌倒的一组动作重复。大部分人1岁就学会了走路,但至此以后的几十年间,或许我们从来没留意过自己行走姿势。当...
JavaScript语言多编程范式简介
和C++等语言类似,JS支持多范式(paradigms)编程。我们常常混合这些范式来完成一些大型Web项目。JS支持3种编程范式:命令式、面向对象和函数式。命令式(Imperative JavaScript)命令式就是简单的从上而下完成任务,流水账过程式编码风格:function
使用CSS3 box-decoration-break特性实现多行文本样式
当文章中的长文本被自动断行为多行文本时,其样式可能会出乎我们的设计。本文介绍如何使用CSS3中的box-decoration-break特性来处理多行元素样式。
按照规范...CSS3弹性布局弹性流(flex-flow)属性详解和实例
弹性布局是CSS3引入的强大的布局方式,用来替代以前Web开发人员使用的一些复杂而易错hacks方法(如使用float进行类似流式布局)。其中flex-flow是flex-direction...
HTTP1.1协议现状、问题和解决方案
HTTP的现状最早的HTTP协议非常简单,只能用来传送文本,方法也只有GET,后来逐步发展到1.1,能够支持多种MIME格式数据(如文本、文件),支持GET,POST,HEAD,OPTI...
Three.js 对象局部坐标转换为世界坐标
在Three.js中进行顶点几何计算时,一个需要注意的地方是,需要统一坐标系。比如你通过Three.js提供的API创建了一个球体网孔对象,那么默认情况下,各网孔顶点的...
三维向量的简单运算和实用意义
在WebGL的实际应用中我们广泛使用向量的几何运算来计算角度、距离,判断点线、点面之间的关系,比如物体之间的碰撞检测。本文简要介绍三维计算机图形学中常用的...
WebGL 纹理映射模式以及WRAP_S | WRAP_T参数详解
我们在纹理滤镜一文中已经说明了2个重要的纹理参数,用来定义对象缩放时纹理的处理方式:GL_TEXTURE_MIN_FILTERGL_TEXTURE_MAG_FILTER本文讲解其余几个纹理参数...
WebVR简介和常用资源链接
什么是WebVR这是一个实验性的JavaScript API,提供了在用户网页浏览器中访问虚拟现实设备的统一接口。当前主流VR设备如Oculus Rift DK2、谷歌的CardBoard、三星...
CSS3图片混合(Blend)效果及其参考计算公式一览表
在Photoshop软件中,混合是将两个图层的色彩值进行合成,从而创造出大量的效果。在这些效果的背后实际是一些简单的数学公式在起作用。下面所介绍的公式仅适用于R...
使用Canvas绘制完美的不完美圆形
真实世界是不完美的,当我们需要模拟真实世界时,经常需要引入不完美/不规则的形状。比如陨石、雨滴、行星、树叶、绵延的海岸线、云朵等。本文介绍如何基于Canva...
更多...