canvas差分函数的妙用

秋涛小哥_1424

秋涛小哥_1424

2016-11-07

1915人浏览

原创

在这篇文章中我们就分析这种效果是如何实现的,如果你对源码比较懵逼,相信看完解析就会恍然大悟。先上效果图:

3686196226-57a94a892fcd0_articlex.gif

1.原理分析

相比与上篇文章中简陋的水波动画的效果,本文的动画效果不仅能够和鼠标进行交互,而且波浪的形成更加自然,更加符合物理规律。整个动画的形成过程就如动图中所展示的那样,在液面的位置点击鼠标,此处的液面就会有一个比较大的起伏,然后此处的震动会向两边传播,随着能量的衰减,后面的震动幅度会越来越下,最后能量衰减到零,页面趋于平静。听上去是不是很玄乎,感觉很高深!毛主席告诉我们千万不要被物体的表面现象所迷惑(谁知道是谁说的呢o(^▽^)o)。下面我们就来一步一步的分析,这其中的原理。

首先,在静止状态下我们可以看到整个液面就相当于是个矩形。而当我们点击液面的位置时,这个矩形就发生了相应的变化。但其实并不是整个矩形都发生了变化,而只是矩形的上边发生了变化。那是如何做到仅仅让矩形的上边发生变化的呢?秘诀就在矩形的上边并不是简单的从左边的点lineTo()到右边的点。而是由很多的点lineTo()组成。这样讲可能不太好理解,看图说话:

892670031-581ee02140c15_articlex.png

在上部我们设置了很多的点,这些点的纵坐标都是一样的,只是在水平方向相隔一定的间距。这样在静止的状态下,我们就可以它看见与普通的矩形别无二致。而改变这些点的位置时我们就能同时改变矩形的形状,从而形成不同的效果。

2.差分方程

说到差分方程也许很多人会头疼,不过也没本法,疼就疼会吧!这个知识点在高数里讲微分方程那一节,如果不明白,就算了吧!记住下面的用法也不错,不过为了逼格我们还是简单的介绍下。

在数学上,递推关系(recurrence relation),也就是差分方程(difference equation),是一种递推地定义一个序列的方程式:序列的每一项目是定义为前一项的函数。某些简单定义的递推关系式可能会表现出非常复杂的(混沌的)性质,他们属于数学中的非线性分析领域。

记住一点,序列的每一项是定义为前一项的函数,我们用的就是这个原理。他的图像如果用matalab来绘制就是下面这样:

892670031-581ee02140c15_articlex.png

只关注原函数,红色的那条曲线就可以了,是不是特别像水波。我们要做的就是让那一堆点按照这样的波形去排列。

3.代码实现

1.准备工作

下面就到了大家最喜欢的代码时间。首先,我们创建一个点类Vertexes, 它的作用就是定义并更新那一堆点,代码在vertex.js中,如下:

function Vertex(x,y,baseY){
        this.baseY = baseY;         //基线
        this.x = x;                 //点的坐标
        this.y = y;            
        this.vy = 0;                //竖直方向的速度
        this.targetY = 0;           //目标位置
        this.friction = 0.15;       //摩擦力
        this.deceleration = 0.95;   //减速
    }
//y坐标更新
Vertex.prototype.updateY = function(diffVal){
        this.targetY = diffVal + this.baseY;   //改变目标位置
        this.vy += (this.targetY - this.y);       //速度
        this.vy *= this.deceleration;
        this.y += this.vy * this.friction;     //改变坐标竖直方向的位置
    }

我们要用这个函数去创建那一堆点。回到我们的主文件index.js中。我们先初始化一些要用的东西:

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    W = window.innerWidth;
    H = window.innerHeight;

    canvas.width = W;
    canvas.height = H;

var color1 = "#6ca0f6",    //矩形1的颜色
    color2 = "#367aec";   //矩形2的颜色
    
var vertexes = [],    //顶点坐标
    verNum = 250,     //顶点数
    diffPt = [],      //差分值

然后,创建点并把它push进vertexes中,同时也创建相应数量的差分值,同样把它放到diffPt数组中,这样每个点都有了对应的差分值。

for(var i=0; i<vernum><p>结果是,每个顶点的y坐标都在(H/2)的高度,水平坐标每隔一定的间隔取一个点。在这里是每隔4.5个像素取一个点,这与你canvas的宽度和点的数目有关。这样我们就把点创建完成了,来绘制一下看看效果。</p>
<p><img src="https://img.php.cn//upload/image/586/956/592/1478510955955550.png?x-oss-process=image/resize,p_40" title="1478510955955550.png" alt="892670031-581ee02140c15_articlex.png"></p>
<p>代码如下:</p><div class="aritcle_card flexRow artxards">
											<div class="artcardd flexRow">
												<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2212" title="Canva可画(Windows版)"><img
														src="https://img.php.cn/upload/manual/001/503/042/6a01bda2e5707946.png" alt="Canva可画(Windows版)" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
												<div class="aritcle_card_info flexColumn">
													<a rel="nofollow" href="/xiazai/gongju/2212" title="Canva可画(Windows版)" class="overflowclass">Canva可画(Windows版)</a>
													<p class="overflowclass">Windows版Canva可画,适用于Windows 10及以上版本。无需担心网络波动,无需顾虑浏览器兼容性,让你能够更加专注在设计上。</p>
												</div>
												<a rel="nofollow" href="/xiazai/gongju/2212" title="Canva可画(Windows版)" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
												</a>
											</div>
										</div>
<pre class="brush:js;toolbar:false">function draw(){
        
        //矩形1
        ctx.save()
        ctx.fillStyle = color1;
        ctx.beginPath();
        ctx.moveTo(0, H);
        ctx.lineTo(vertexes[0].x, vertexes[0].y);
        for(var i=1; i<vertexes.length><p>就像你看到的那样此时我们的液面完全是静止的(因为没更新点嘛)。之所以要绘制两个矩形,你看看效果图就明白了,只是为了更好看,你完全可以绘制第三层,第四层。下面我们就来更新这些点的坐标。</p>
<p style="box-sizing: border-box; font-family: " helvetica neue arial sc sans gb micro hei yahei sans-serif font-weight: line-height: color: rgb margin: font-size: white-space: normal background-color:>2.核心代码</p>
<p>点的更新我们放在了update函数中。首先,我们设置一个初始的震荡点,缓冲变量和初始差分值。</p>
<pre class="brush:js;toolbar:false;">var vPos = 125;  //震荡点
var dd = 15;     //缓冲
var autoDiff = 1000;  //初始差分值

这里的震荡点就是我们的起震位置,意思是vertexes中的第125号点开始起震,它对应的差分值就是autoDiff。它的改变会引起其他点的变化,从而达到更新其他差分值的效果。

function update(){
        autoDiff -= autoDiff*0.9;        //1
        diffPt[vPos] = autoDiff;         

        //左侧
        for(var i=vPos-1; i>0; i--){     //2
            var d = vPos-i;
            if(d > dd){
                d=dd;
            }
            diffPt[i]-=(diffPt[i] - diffPt[i+1])*(1-0.01*d);
        }
        //右侧
        for(var i=vPos+1; i<vernum>dd){
                d=dd;
            }
            diffPt[i] -= (diffPt[i] - diffPt[i-1])*(1-0.01*d);
        }

        //更新Y坐标
        for(var i=0; i<vertexes.length><p>现在我们对上面的部分做详细解释:<br>代码1: 我们设置了起震位置的差分偏移量为autoDiff=100,注意autoDiff -= autoDiff*0.9;, 也就是说它的值每一帧都会变化。</p>
<p>代码2:为起震位置的左边,主要关注diffPt[i]-=(diffPt[i] - diffPt[i+1])*(1-0.01*d);这一行。i的起始位置为124124,默认差分值为0。稍作简单推算,你会发现,经过更新后第124124号点的差分值为99,同理第123号为97.02。以此类推,我们就可以得到第一帧的所有点的差分值。右边同理。</p>
<p>代码4:在得到第一帧的差分值后就该调用每个点的更新函数了,并且传入计算好的差分值。形成的效果如下图所示</p>
<p><img src="https://img.php.cn//upload/image/781/589/196/1478511013872442.png?x-oss-process=image/resize,p_40" title="1478511013872442.png" alt="892670031-581ee02140c15_articlex.png"></p>
<p>看一下updateY函数,我们把目标位置targetY设置为差分值diffVal和基线baseY的和。然后,通过距离计算需要运动的速度vy,最后将速度作用于点的纵坐标。这一段是不是与弹性动画缓动动画那一节很相似呢?</p>
<p>在缓冲系数dd的作用下,两侧的波会在扩散的过程中越来越小,最后趋近于0.我们也是通过这个变量去控制液体的粘度系数,达到粘稠度高的物体扩散的越缓慢并且起伏比较低,粘稠度低的物体扩散迅速但起伏大的效果。</p>
<p>随后,因为autoDiff的不断衰减,不同幅值波形的叠加形成波浪效果,最终衰减到0.液面也就趋于平静了。</p>
<p>现在,我们把update()和draw()放入动画循环中你就会看到水波起伏然后趋于平静的效果。</p>
<pre class="brush:html;toolbar:false">(function drawframe(){
        ctx.clearRect(0, 0, W, H);
        window.requestAnimationFrame(drawframe, canvas);
        update()
        draw();
    })()

3.鼠标交互

上面的代码已经实现了波浪动画的效果,但是震荡完成后就平静了,不会再发生震荡的效果。这一步我们就来实现点哪,哪震的效果。实现的思路很简单:水波之所以区域平静是因为起震位置的差分值不断衰减的结果,我们只需要在点击鼠标的位置重设autoDiff就可以了。此外,起震点的位置也要变成鼠标点击的位置。代码如下:

canvas.addEventListener('mousedown', function(e){
        var mouse = {x:null, y:null};

        if(e.pageX||e.pageY){
            mouse.x = e.pageX;
            mouse.y = e.pageY;
        }else{
            mouse.x = e.clientX + document.body.scrollLeft +document.documentElement.scrollLeft;
            mouse.y = e.clientY + document.body.scrollTop +document.documentElement.scrollTop;
        }

        //重设差分值
        if(mouse.y>(H/2-50) && mouse.y<p>在获取鼠标位置这里应该注意一点,我们没有减去canvas的偏移量,这是因为在这里canvas做的是全屏设置。所以,如果你的画布并不是全屏大小,建议你使用我们的utils.js文件中的方法captureMouse来获取鼠标的坐标。</p><p>另外在判断鼠标是否点击在了液面上,我们设定了一个比较宽的范围,上下共100px。这样做的目的是让用户很容易就能触发这个事件,而不是只在页面那唯一的一个值上才能触发。这种做法相信你以前做过,对于比较小的物体我们会遮罩一个大一些的透明物体,然后在该物体上做事件的触发,便于用户操作。</p><p><br></p>

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

2026.11.12

80

15

Go-Zero安装与goctl环境配置教程
Go-Zero安装与goctl环境配置教程

本专题围绕Go-Zero的安装与工具链配置展开,讲清Go版本要求、goctl安装方式、代理镜像、protoc依赖与环境变量设置,说明多版本共存、升级回退与CI流水线安装的做法,并整理安装阶段的常见报错与验证步骤。

2026.11.12

60

15

Go-Zero零基础入门教程
Go-Zero零基础入门教程

本专题整理Go-Zero的入门知识,从框架定位、环境准备到跑通第一个API服务,讲清目录分层、配置加载、日志与中间件的基本用法,并对比Gin、Kratos的差异,帮助Go开发者建立整体认知,判断它是否适合当前项目。

2026.11.12

40

15

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

2026.11.06

140

12

Postman请求头Headers怎么设置
Postman请求头Headers怎么设置

围绕Postman HTTP请求头配置展开,讲解Headers选项的具体使用方式,包括Key和Value参数填写、请求头信息添加、常见Header字段设置及请求头修改方法,帮助用户解决接口调试过程中遇到的请求头配置问题。

2026.11.06

120

13

Postman接口请求发送教程
Postman接口请求发送教程

详细介绍Postman接口请求的创建与发送方法,涵盖GET和POST请求、URL配置、请求参数、Headers请求头、Body请求体设置以及响应结果查看,帮助用户掌握Postman接口请求的完整操作流程。

2026.11.06

120

10

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

2026.11.05

100

12

DBeaver安全防护教程大全
DBeaver安全防护教程大全

DBeaver安全防护专题,围绕数据库连接安全、账号密码保护和访问权限管理展开,整理SSL/TLS加密连接、SSH隧道、数据库凭据保存及证书验证等配置方法,同时介绍敏感信息保护、远程连接风险和高权限账号使用等注意事项,帮助用户提高数据库日常管理的安全性。

2026.11.05

80

12

DBeaver连接创建与配置教程
DBeaver连接创建与配置教程

本专题聚焦DBeaver数据库连接配置与管理,覆盖从新建连接、选择数据库类型到填写连接参数和验证连接的完整流程。针对MySQL、PostgreSQL、SQL Server等常见数据库,整理相应的连接设置方法,并提供驱动缺失、认证失败和连接异常等问题的排查方向。

2026.11.04

100

16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Canvas 绘制时钟
Canvas 绘制时钟

共7课时 | 1.7万人学习

HTML5新特性基础视频教程
HTML5新特性基础视频教程

共18课时 | 3.8万人学习

HTML5 Canvas 动画实战教程
HTML5 Canvas 动画实战教程

共28课时 | 7.2万人学习