canvas笔记

yang

canvas代码

基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #canvas {
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">

</canvas>
<script>
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");

    context.beginPath();
    context.arc(100, 100, 50, 0, Math.PI * 2, true);
    context.closePath();
    context.fillStyle = 'rgb(255,255,255)';
    context.fill();
</script>
</body>
</html>
也可使用js设置canvas的宽高,以及所代表的的意思
<template>
 <canvas id="canvas"  ref="canvas"></canvas>
</template>

<script>
  export default {
    methods: {
      init() {
        let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')//获取到 Canvas 的上下文环境 代表一个二维渲染上下文
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() // 起始一条路径,或重置当前路径
        context.arc(100,100,50,0,Math.PI*2,true)// 创建弧/曲线
        context.closePath()// 创建从当前点回到起始点的路径
        context.fillStyle = 'rgb(255,255,255)' // 设置或返回用于填充绘画的颜色、渐变或模式
        context.fill()// 填充当前绘图(路径)
      }
    },
    mounted () {
      this.init()
    },
  }
</script>

<style lang="scss" scoped>
#canvas{
  background-color: #000;
}
</style>

注意
*不要使用 CSS 设置。因为默认创建一个 300 150 的画布,
如果使用 CSS 来设置宽高的话,画布就会按照 300 150 的比例进行缩放,也就是将 300 150 的页面显示在 400 400 的容器中*

绘制路径

image.png

使用 Canvas 绘制图像的步骤
image.png

绘制弧/曲线

arc() 方法创建弧/曲线(用于创建圆或部分圆)。

context.arc(x,y,r,sAngle,eAngle,counterclockwise);

  • x:圆心的 x 坐标
  • y:圆心的 y 坐标
  • r:圆的半径
  • sAngle:起始角,以弧度计(弧的圆形的三点钟位置是 0 度)
  • eAngle:结束角,以弧度计
  • counterclockwise:可选。规定应该逆时针还是顺时针绘图。false 为顺时针,true 为逆时针

image.png

画一个顺时针的四分之一圆

let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() 
        context.arc(100,100,50,0,Math.PI*0.5,false)
        context.strokeStyle = "white"
        context.stroke()

因为我们设置的起始角是 0,对照 w3cschool 上的截图可知弧度的 0 的位置是 3 点钟方向,然后结束角我们设置为 0.5 PI,也就是 6 点钟方向

stroke()和fill()的区别

  • stroke() :描边
  • fill() :填充

我们可以通过 strokeStyle属性 和 fillStyle属性来设置描边和填充的颜色

绘制直线

let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() 
        context.moveTo(50,50)
        context.lineTo(100,100)
        context.strokeStyle = "white"
        context.stroke()
  • moveTo(x,y):把路径移动到画布中的指定点,不创建线条
  • lineTo(x,y):添加一个新点,然后在画布中创建从该点到最后指定点的线条

这里需要注意以下几点:

  • 如果没有 moveTo,那么第一次 lineTo 的就视为 moveTo
  • 每次 lineTo 后如果没有 moveTo,那么下次 lineTo 的开始点为前一次 lineTo 的结束点。

也就是这种情况:

 let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() 
        context.lineTo(100,100)
        context.lineTo(50,100)
        context.lineTo(200,200)
        context.lineTo(20,10)
        context.strokeStyle = "white"
        context.stroke()

我们没有设置 moveTo,而是设置了三个 lineTo,这也是可以的,将三个 lineTo 设置的点依次连接就好~

给绘制的直线添加样式

image.png
其宽度设置为 10,并且加上“圆角”的效果

let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() 
        context.moveTo(50,100)
        context.lineTo(200,200)
        context.lineWidth = 10
        context.lineCap = 'round'
        context.strokeStyle = "white"
        context.stroke()

image.png

绘制矩形

 let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() 
        context.fillStyle = '#fff'
        context.fillRect(10,10,100,100)
        context.strokeStyle = '#fff'
        context.strokeRect(130,10,100,100)

image.png

  • fillRect(x,y,width,height):绘制一个实心矩形
  • strokeRect(x,y,width,height):绘制一个空心矩形

颜色、样式和阴影

image.png

  let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        context.beginPath() 
        context.arc(100,100,50,0,2*Math.PI,false);
        context.fillStyle = '#fff';
        context.shadowBlur = 20
        context.shadowColor = "#fff"
        context.fill()

image.png

设置渐变

image.png
绘制渐变主要用到了 createLinearGradient() 方法,我们来看一下这个方法:context.createLinearGradient(x0,y0,x1,y1);

  • x0:开始渐变的 x 坐标
  • y0:开始渐变的 y 坐标
  • x1:结束渐变的 x 坐标
  • y1:结束渐变的 y 坐标
let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        var grd = context.createLinearGradient(100,100,100,200)
        grd.addColorStop(0,'pink');
        grd.addColorStop(1,'lightBlue')
        context.fillStyle = grd;
        context.fillRect(100,100,200,200)

image.png
createLinearGradient() 的参数是两个点的坐标,这两个点的连线实际上就是渐变的方向。我们可以使用 addColorStop() 方法来设置渐变的颜色。

gradient.addColorStop(stop,color);:

  • stop:介于 0.0 与 1.0 之间的值,表示渐变中开始与结束之间的位置
  • color:在结束位置显示的 CSS 颜色值
 let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
        var grd = context.createLinearGradient(0,0,0,400)
        grd.addColorStop(0,'pink');
        grd.addColorStop(0.2,'lightBlue')
        grd.addColorStop(0.4,'red')
        grd.addColorStop(0.6,'pink')
        grd.addColorStop(0.8,'black')
        grd.addColorStop(1,'yellow')
        context.fillStyle = grd;
        context.fillRect(0,0,400,400)

image.png

图形转换

image.png

let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
       context.strokeStyle = 'white'
       context.strokeRect(5,5,50,25)
       context.scale(2,2)
       context.strokeRect(5,5,50,25)
       context.scale(2,2)
       context.strokeRect(5,5,50,25)

image.png

旋转

let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        let cx = canvas.width = 400
        let cy = canvas.height = 400
       context.fillStyle = 'white'
       context.rotate(20*Math.PI/180)
       context.fillRect(70,30,200,100)

image.png

context.rotate(angle);

  • angle : 旋转角度,以弧度计。如需将角度转换为弧度,请使用 degrees*Math.PI/180 公式进行计算。举例:如需旋转 5 度,可规定下面的公式:5*Math.PI/180
  • 、我们将画布旋转了 20°,然后再画了一个矩形。

在进行图形变换的时候,我们需要画布旋转,然后再绘制图形,
使用的图形变换的方法都是作用在画布上的,既然对画布进行了变换,那么在接下来绘制的图形都会变换。
比如我对画布使用了 rotate(20*Math.PI/180) 方法,就是将画布旋转了 20°,然后之后绘制的图形都会旋转 20°。

图像绘制

Canvas 还有一个经常用的方法是drawImage()
image.png
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);

  • img:规定要使用的图像、画布或视频
  • sx:可选。开始剪切的 x 坐标位置
  • sy:可选。开始剪切的 y 坐标位置
  • swidth:可选。被剪切图像的宽度
  • sheight:可选。被剪切图像的高度
  • x:在画布上放置图像的 x 坐标位置
  • y:在画布上放置图像的 y 坐标位置
  • width:可选。要使用的图像的宽度(伸展或缩小图像)
  • height:可选。要使用的图像的高度(伸展或缩小图像)

炫酷背景特效的通性

  • 背景

    • 单一颜色
    • 渐变
    • 平铺
  • 炫酷

    • 随机
  • 特效(与用户交互)

    • 鼠标跟随
    • 视觉差
背景

景往往是纯色的或者是渐变的,再或者就是有规律的可以平铺的图形。
封面图一般都是一个渐变的背景 + 文字。简洁却不简单
过渡色获取:uigradients
这个网站可以自己生成渐变色,你的配色也可以跟大家分享,可以保存为图片,也可以导出为 CSS 样式。

我们可以从这个网站上找到喜欢的配色,然后导出为 CSS 样式使用。
image.png
星空背景的渐变实际上不是使用 Canvas 写的,只是使用 CSS 写出的效果。实现的方式是:

下面的树是一个 png 的背景
image.png
然后我们将 body 的颜色设置为黑色到蓝色的由上向下的渐变:

background:linear-gradient(to bottom,#000000 0%,#5788fe 100%)

接下来我们要设置一个全屏的遮罩,将这个遮罩的背景色设置为红色,然后使用 CSS3 的 animation 属性,使用 animation 改变其透明度,由 0 变为 0.9。

.filter{
    width:100%;
    height:100%;
    position:absolute;
    top:0;
    left:0;
    background:#fe5757;
    animation:colorChange 30s ease-in-out infinite;
    animation-fill-mode:both;
    mix-blend-mode:overlay;
}
@keyframs colorChange{
    0%,100%{
     opacity:0;
    }
    50%{
    opacity:.9;
    }
}

效果就和上面动态的效果一样。

炫酷
  • 随机

让元素动起来

*   gif 图
    
*   CSS3 动画
    
*   js 控制
    
*   svg
    
*   Canvas

随机
使用 gif 图大家都知道,只能是有规律的“动”,并且 gif 图片的尺寸不宜过大,在我们的网页背景中,基本上是不会用到的。

CSS3 实现的动画效果,也是只能做有规律的“动”,并且 CSS 只能操纵单个的 DOM 元素,一旦元素到达一定的数量,代价是比较大的。

所以我们选择 js + Canvas 来实现“随机”的“动”。

效果

主要是与鼠标之间的交互效果。

与鼠标之间有互动的效果主要是产生用户行为的反馈,比如在网页制作中,我们经常使用 hover 变色表示用户的鼠标在元素上方悬停。这就是用户行为的一种反馈。

我们经常使用的与鼠标之间的交互效果主要有两种:

  • 鼠标跟随
  • 视觉差
    用户很喜欢这种鼠标跟随的效果,个人觉得就是因为它使得网站的显示效果和用户的行为产生了联系,使用户的行为得到了反馈。

还有一种经常见到的效果是数据差的效果,比如:

视觉差效果

这是锤子官网的一个特效,鼠标移动到哪哪就会下沉,并且如果你仔细看的话就会发现,上面的月份数字和底部的图片不是在一个层级上的,更加有立体的感觉,这就是视觉差的特效。

这种特效不需要用 Canvas,只需要 CSS 就可以实现

怎么实现随机粒子

如果只是一个纯色或者渐变的背景,肯定会显得有点单调,我们还需要在渐变的基础上加一点 “料”,而这些 “料”通常都是粒子特效。 那么“粒子特效” 都有什么特点呢?

  • 粒子
  • 规则图形
  • 随机
  • 数量多

将无数的单个粒子组合使其呈现出固定形态,借由控制器,脚本来控制其整体或单个的运动,模拟出现真实的效果。

粒子特效的首要特点是数量多,在物理学中,粒子是能够以自由状态存在的最小物质组成部分,所以粒子的第一个特点就是数量多。

粒子特效的第二个特点是运动,正是因为组成物体的粒子在不断运动,我们才能看到物体在不断运动。

粒子特效第三个特点是随机,排列有整齐排列之美,凌乱有凌乱之美,整齐排列的可以直接平铺背景实现,直接使用 img 图片就可以。

但是要想有随机效果使用 img 图片就不可以了,所以我们主要使用 Canvas 实现随机粒子效果。各项参数都是随机生成的。
现在我们来一起实现一个随机粒子特效。

效果如下:

image.png

创建全屏 Canvas

首先,我们需要一个全屏的 Canvas 画布

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,
        body {
            margin: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
            background-color: #000;
        }
    </style>

</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        WIDTH, HEIGHT;
    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;
    initRoundPopulation = 80,
    WIDTH, HEIGHT;
   
</script>

</html>

我们使用 WIDTHHEIGHT 两个常量储存屏幕宽度和高度信息,我们习惯使用大写来表示改变量为常量,不可变,将屏幕宽度和高度信息储存在常量中是因为我们在稍后还会用到。

这时,你应该得到一个全屏的并且为黑色的 Canvas。

设置 Round_item

创建单个的 Round_item 类。
要设置的是位置随机、透明度随机、半径随机的圆。为了区分不同的圆,我们还应该设置一个唯一的 index 参数。

所以我们需要的参数有:

  • x 坐标
  • y 坐标
  • 半径
  • 透明度
  • index

根据上面这些可以得出我们的 Round_item 类:

 function Round_item(index,x,y){
        this.index = index
        this.x = x;
        this.y = y;
        this.r = Math.random()*2+1; //随机半径
        var alpha = (Math.floor(Math.random()*10)+1)/10/2
        this.color = "rgba(255,255,255,"+alpha+")"
    }

这里我们使用了构造函数的方式来创建单个的圆,我们还需要一个变量 initRoundPopulation 来设置 round 的个数,然后我们便可以通过 for 循环创建出 initRoundPopulation 个圆。

设置 draw() 方法

在设置了单个的 Round_item 类之后,我们还要给每一个 round 设置 draw() 方法,所以我们需要将 draw() 方法设置在 Round_item 的原型中,这样我们创建出来的每一个 Round_item 实例对象都拥有了 draw() 方法。

 Round_item.prototype.draw = function(){
        content.fillStyle = this.color;
        // shadowBlur阴影的模糊级别
        content.shadowBlur = this.r*2;
        content.beginPath()
        // x 坐标 y坐标 半径 起始角 结束角   顺时针逆时针
        content.arc(this.x,this.y,this.r,0,2*Math.PI,false)
        content.closePath()
        content.fill()
    }

设置初始化 init() 函数

然后我们就需要设置初始化 init() 函数了,在 init() 函数中,我们的主要任务是创建单个的 round,然后使用其 draw() 方法。

function init(){
        for (var i = 0; i < initRoundPopulation; i++) {
            round[i] = new Round_item(i,Math.random()*WIDTH,Math.random().HEIGHT)
            round[i].draw()
        }
    }

至此,我们已经完成了随机粒子的实现,完整的代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,
        body {
            margin: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
            background-color: #000;
        }
    </style>

</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        initRoundPopulation = 80,
        round = [],
        WIDTH, HEIGHT;
    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;
    ctx.width = WIDTH;
    ctx.height = HEIGHT;
    function Round_item(index,x,y){
        this.index = index
        this.x = x;
        this.y = y;
        this.r = Math.random()*2+1; //随机半径
        var alpha = (Math.floor(Math.random()*10)+1)/10/2
        this.color = "rgba(255,255,255,"+alpha+")"
    }
    Round_item.prototype.draw = function(){
        content.fillStyle = this.color;
        // shadowBlur阴影的模糊级别
        content.shadowBlur = this.r*2;
        content.beginPath()
        // x 坐标 y坐标 半径 起始角 结束角   顺时针逆时针
        content.arc(this.x,this.y,this.r,0,2*Math.PI,false)
        content.closePath()
        content.fill()
    }

    function init(){
        for (var i = 0; i < initRoundPopulation; i++) {
            round[i] = new Round_item(i,Math.random()*WIDTH,Math.random().HEIGHT)
            round[i].draw()
        }
    }
    init()
</script>

</html>

使你的随机粒子动起来

animate() 函数

Canvas 制作动画是一个不断擦除再重绘的过程,跟最原始实现动画的方式类似。在纸片上画每一帧,然后以很快的速度翻动小本本,就会有动画的效果。
现在我们实现动画需要在很短的时间内不断的清除内容再重新绘制,新的图形和原先清除的图形之间有某种位置关系,速度足够快的话,我们就会看到动画的效果。
所以我们需要一个 animate() 函数,这个函数的作用是帮助我们形成动画,我们在这个函数中首先需要清除当前屏幕,这里的清除函数用到的是 content.clearRect() 方法。

canvas 的 content.clearRect() 方法:

context.clearRect(x,y,width,height);

  • x:要清除的矩形左上角的 x 坐标
  • y:要清除的矩形左上角的 y 坐标
  • width:要清除的矩形的宽度,以像素计
  • height:要清除的矩形的高度,以像素计

我们需要清除的区域是整个屏幕,所以 content.clearRect() 的参数就是 content.clearRect(0, 0, WIDTH, HEIGHT);,这里我们就用到了之前获取的屏幕宽度和高度的常量:WIDTHHEIGHT

粒子匀速上升。粒子匀速上升,也就是 y 坐标在不断地变化,既然是匀速的,那么也就是在相同的时间位移是相同的。
重新绘制完图形之后,我们就完成了清除屏幕内容再重新绘制新的图形的任务。那么还需要有一个步骤 —— “

不断”,要想实现动画的效果,就需要 “不断” 地进行清除再重绘,并且中间间隔的时间还不能过长。
js 的 setTimeout() 方法,但是 setTimeoutsetInterval 的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器 UI 线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。

另外一个函数 —— requestAnimationFrame()

window.requestAnimationFrame() 方法告诉浏览器,你希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。

requestAnimationFrame() 函数可以说是专门用来写动画的。
编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。

大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约等于 16.6ms

requestAnimationFrame 采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

使用 requestAnimationFrame() 函数递归的调用 animate() 函数来实现动画的效果。

 function animate(){
        content.clearRect(0,0,WIDTH,HEIGHT)
        for(var i in round){
            round[i].move()
        }
        requestAnimationFrame(animate)
    }

创建 move() 函数

使用 move() 函数来改变 round 的 y 坐标
move() 方法写在 Round_item 的原型上,这样我们创建的每一个 round 都具有了 move() 方法。

move() 方法中,我们只需要改变 round 的 y 坐标即可,并且设置边界条件,当 y 坐标的值小于 -10(也可以是其他负值),代表该 round 已经超出了屏幕,这个时候我们要将其移动到屏幕的最底端,这样才能保证我们创建的粒子数不变,一直是 initRoundPopulation 的值。
这样就是一个粒子在不断地上升,上升到了最顶端再移动到最底端的循环过程,看起来像是有源源不断的粒子,但其实总数是不变的。

在 y 坐标的变化之后,我们还需要使用新的 y 坐标再来重新绘制一下该 round。

init() 中加入 animate()

我们想要实现动画的效果,还需要在 init() 中加入 animate() 函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        html, body {
            margin: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
            cursor: none;
            background: black;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>

<script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        round = [],
        WIDTH,
        HEIGHT,
        initRoundPopulation = 80;


    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;

    ctx.width = WIDTH;
    ctx.height = HEIGHT;

    function Round_item(index, x, y) {
        this.index = index;
        this.x = x;
        this.y = y;
        this.r = Math.random() * 2 + 1;
        var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
        this.color = "rgba(255,255,255," + alpha + ")";
    }

    Round_item.prototype.draw = function () {
        content.fillStyle = this.color;
        content.shadowBlur = this.r * 2;
        content.beginPath();
        content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
        content.closePath();
        content.fill();
    };

    function animate() {
        content.clearRect(0, 0, WIDTH, HEIGHT);

        for (var i in round) {
            round[i].move();
        }
        requestAnimationFrame(animate)
    }

    Round_item.prototype.move = function () {
        this.y -= 0.15;
        if (this.y <= -10) {
            this.y = HEIGHT + 10;
        }
        this.draw();
    };


    function init() {
        for (var i = 0; i < initRoundPopulation; i++) {
            round[i] = new Round_item(i, Math.random() * WIDTH, Math.random() * HEIGHT);
            round[i].draw();
        }
        animate();

    }

    init();
</script>
</body>
</html>

使你的鼠标和屏幕互动

image.png

鼠标移动,会在经过的地方创建一个圆,圆的半径由小变大,达到某个固定大小时该圆消失。圆的颜色也是在随机变化的

创建 Canvas 元素

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #canvas {
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
        var canvas = document.getElementById('canvas'),
            ctx = canvas.getContext('2d'),
            WIDTH = canvas.width = document.documentElement.clientWidth,
            HEIGHT = canvas.height = document.documentElement.clientHeight,
            para = {
                num: 100,
                color: false,    //  颜色  如果是false 则是随机渐变颜色
                r: 0.9,          //   圆每次增加的半径 
                o: 0.09,         //      判断圆消失的条件,数值越大,消失的越快
                a: 1
            },
            color,
            color2,
            round_arr = [];     // 存放圆的数组 
</script>
</body>
</html>

onmousemove 事件

在鼠标移动的过程中,不断地在鼠标滑过的位置产生一个逐渐变大的圆。

Canvas 中创建动画的方式就是不断地清除屏幕内容然后重绘。

移动的轨迹是由一个一个的圆构成的,如果移动的速度过快的话,那么就可以明显看出一个一个的圆。

既然轨迹是由很多圆构成,那么我们就应该使用数组储存圆的信息(坐标、半径),然后在鼠标移动的时候将鼠标的位置信息储存在数组中。

所以在鼠标移动的过程我们首先要获得鼠标的坐标,然后将鼠标的坐标以及其他信息 push 到数组中去:

window.onmousemove = function(event){
                mouseX = event.clintX;
                mouseY = event.clientY;
                round_arr.push({
                    mouseX:mouseX,
                    mouseY:mouseY,
                    r:para.r,//设置半径每次增大的数值
                    o:l //判断圆消失的条件  数值越大  消失的越快
                })
            }

设置 color

已经将圆的相关信息储存在 round_arr 数组中了,现在要在 animate() 函数中将圆显示出来。
创建圆需要的坐标信息以及半径,我们在鼠标移动的事件中都已经将其 push 到 round_arr 数组中了,还有一个条件是需要设置的,那就是颜色。
para 参数中,我们可以看出,其中有设置 color 值。如果 color 值不为 false,那么设置的圆的颜色就是设置的 color 值;如果设置的 color 值为 false,那么圆的颜色就是随机的。

if(para.color){
                color2 = para.color
            }else{
                color = Math.random()*360
            }

那么怎么设置颜色的渐变呢?我们将 color 的颜色值依次增加一个增量。

if (!para.color) {
    color += .1;
    color2 = 'hsl(' + color + ',100%,80%)';
}

要让颜色一直改变,我们要将上面颜色改变的代码放在一个一直执行的函数。将上面改变颜色的代码放在animate() 函数中。

animate() 函数

一个一直在执行的函数,这个函数主要负责动画的 animate() 函数。从函数名就可以看出这个函数的作用,的确,我们需要在该函数中写动画。
清除屏幕再重新绘制,

ctx.clearRect(0, 0, WIDTH, HEIGHT);

接着使用 round_arr 数组中的数据将一个一个的圆绘制出来。

然后我们还需要一直执行这个函数:

window.requestAnimationFrame(animate);
  1. 创建 Canvas 元素,设置参数
  2. 鼠标移动事件,将坐标信息 push 到数组
  3. 设置颜色
  4. 设置动画 animate() 函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #canvas {
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>


        var canvas = document.getElementById('canvas'),
            ctx = canvas.getContext('2d'),
            WIDTH = canvas.width = document.documentElement.clientWidth,
            HEIGHT = canvas.height = document.documentElement.clientHeight,
            para = {
                num: 100,
                color: false,    //  颜色  如果是false 则是随机渐变颜色
                r: 0.9,
                o: 0.09,         //  判断圆消失的条件,数值越大,消失的越快
                a: 1,

            },
            color,
            color2,
            round_arr = [];





        window.onmousemove = function (event) {

            mouseX = event.clientX;
            mouseY = event.clientY;

            round_arr.push({
                mouseX: mouseX,
                mouseY: mouseY,
                r: para.r,
                o: 1
            })
        };


        // 判断参数中是否设置了 color,如果设置了 color,就使用该值、
        // 如果参数中的 color 为 false,那么就使用随机的颜色
        if (para.color) {
            color2 = para.color;
        } else {
            color = Math.random() * 360;
        }

        function animate() {

            if (!para.color) {
                color += .1;
                color2 = 'hsl(' + color + ',100%,80%)';
            }

            ctx.clearRect(0, 0, WIDTH, HEIGHT);

            for (var i = 0; i < round_arr.length; i++) {

                ctx.fillStyle = color2;
                ctx.beginPath();
                ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
                ctx.closePath();
                ctx.fill();
                round_arr[i].r += para.r;
                round_arr[i].o -= para.o;

                if( round_arr[i].o <= 0){
                    round_arr.splice(i,1);
                    i--;
                }
            }

            window.requestAnimationFrame(animate);
        };

        animate();
</script>
</body>
</html>

image.png

canvas特效

  • 背景颜色不宜过多
  • 粒子数量多
  • 粒子在动
  • 能和鼠标进行交互

背景颜色

因为网站还是以阅读为主,所以网站的背景颜色要适合阅读,最好还是设置为传统的 “白纸黑字”,使用浅色颜色作为背景,同时饱和度不宜过高,最好设置透明度。

并且背景颜色最好是 1~2 种颜色,不要设置过多的颜色,不然会影响阅读。

背景颜色可以直接使用 CSS 样式设置,不需要使用 Canvas。

粒子在动

大多数用户比较喜欢动效,但是对于网页的背景来说,动作的幅度又不能太大,动作也不要过于复杂,只是一些简单的位移并且动作的幅度也要小一点,让用户的潜意识里面知道这些粒子在动就可以,不能使用户的全部注意力都在粒子上面而忽视了网页的内容

和鼠标进行交互

用户一般还喜欢自己的操作能够得到网页的响应,所以我们可以设置鼠标跟随的效果或者视觉差的效果,加上和鼠标交互的特效,会使用户感到你的网站与众不同。

使你的 Canvas 更加优雅

常见的 Canvas 优化方法

避免浮点数的坐标点

绘制图形时,长度与坐标应选取整数而不是浮点数,原因在于 Canvas 支持半个像素绘制。

会根据小数位实现插值算法实现绘制图像的反锯齿效果,如果没有必要请不要选择浮点数值。

使用多层画布去画一个复杂的场景

一般在游戏中这个优化方式会经常使用,但是在我们的背景特效中不经常使用,这个优化方式是将经常移动的元素和不经常移动的元素分层,避免不必要的重绘。

比如在游戏中,背景不经常变换和人物这些经常变换的元素分成不同的层,这样需要重绘的资源就会少很多。

用 CSS transform 特性缩放画布

如果你使用 lefttop 这些 CSS 属性来写动画的话,那么会触发整个像素渲染流程 —— paintlayoutcomposition

但是使用 transform 中的 translateX/Y 来切换动画,你将会发现,这并不会触发 paintlayout,仅仅会触发 composition 的阶段。

这是因为 transform 调用的是 GPU 而不是 CPU。

离屏渲染

名字听起来很复杂,什么离屏渲染,其实就是设置缓存,绘制图像的时候在屏幕之外的地方绘制好,然后再直接拿过来用,这不就是缓存的概念吗?!︿( ̄︶ ̄)︿.

建立两个 Canvas 标签,大小一致,一个正常显示,一个隐藏(缓存用的,不插入 DOM 中)。先将结果 draw 到缓存用的 canvas 上下文中,因为游离 Canvas 不会造成 UI 的渲染,所以它不会展现出来;再把缓存的内容整个裁剪再 draw 到正常显示用的 Canvas 上,这样能优化不少。

离屏渲染

使用之前的demo

离屏渲染的主要过程就是将一个一个的粒子先在屏幕之外创建出来,然后再使用 drawImage() 方法将其“放入”到我们的主屏幕中。

我们首先要在全局设置一个变量 useCache 来存放我们是否使用离屏渲染这种优化方式。

var useCache = true;

Round_item 方法

然后我们在 Round_item 原型的 draw() 方法中创建每一个离屏的小的 canvas

function Round_item(index,x,y){
            this.index = index;
            this.x = x;
            this.y = y;
            this.useCache = useChache;
            
            this.cacheCanvas = document.createElement('canvas')
            this.cacheCtx = this.cacheCanvas.getContext('2d')
            this.r = Math.random()*2 + 1;
            this.cacheCtx.width = 6* this.r;
            this.cacheCtx.height = 6*this.r;
            var alpha = (Math.floor(Math.random()*10)+1)/10/2;
            this.color = "rgba(255,255,255,"+ alpha+")";
            if(useChache){
                this.cache()
            }
        }

这里的 cacheCanvas 画布的宽度要设置为 6 倍的半径 是因为,我们创建的 cacheCanvas 不仅仅是有圆,还包括圆的阴影,所以我们要将 cacheCanvas 的面积设置得稍微大一些,这样才能将圆带阴影一起剪切到我们的主 Canvas 中。
draw() 方法中,我们新创建了 cacheCanvas,并获取到了 cacheCanvas 的上下文环境,然后设置其宽高。

然后我们判断了 useChache 变量的值,也就是说,如果我们将 useChache 设置为 true,也就是使用缓存,我们就调用 this.cache() 方法

this.cache() 方法

Round_item 的原型中设置 this.cache() 方法。

this.cache() 方法中,我们的主要任务是在每一个 cacheCanvas 中都绘制一个圆。

  Round_item.prototype.cache = function(){
            this.cacheCtx.save();
            this.cacheCtx.fillStyle = this.color;
            this.cacheCtx.shadowColor = "white";
            this.cacheCtx.shadowBlur = this.r*2;
            this.cacheCtx.beginPath();
            this.cacheCtx.arc(this.r*3,this.r*3,this.r,0,2*Math.PI);
            this.cacheCtx.closePath();
            this.cacheCtx.fill();
            // restore(): 用来恢复Canvas旋转、缩放等之后的状态  当和canvas.save( )一起使用时,恢复到canvas.save( )保存时的状态。
            this.cacheCtx.restore();
        }

draw() 方法中画的圆不同之处是,要注意这里设置的圆心坐标,是 this.r * 3,因为我们创建的 cacheCanvas 的宽度和高度都是 6 * this.r,我们的圆是要显示在 cacheCanvas 的正中心,所以设置圆心的坐标应该是 this.r * 3,this.r * 3

draw() 方法

draw() 中,就需要使用 Canvas 的 drawImage 方法将 cacheCanvas 中的内容显示在屏幕上。

 Round_item.prototype.draw = function(){
            if(!useCache){
                content.fillStyle = this.color
                content.shadowBlur = this.r*2
                content.beginPath()
                content.arc(this.x,this.y,this.r,0,2*Math.PI,false);
                content.closePath()
                content.fill()
            }else{
                // drawImage() 方法绘制图像的某些部分,以及/或者增加或减少图像的尺寸 在画布上定位图像,并规定图像的宽度和高度:
                content.drawImage(this.cacheCanvas,this.x-this.r,this.y-this.r)
            }
        }

如果没有使用缓存的话,还是使用最原始的创建圆的方式。

就完成了离屏渲染的优化,我们来一起看一下完整的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>

        html,body{
            width: 100%;
            height: 100%;
            margin:0;
            overflow: hidden;
            cursor: none;
            background-color: #000;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        var ctx = document.getElementById('canvas'),
            content = ctx.getContext('2d'),
            round = [],
            WIDTH,HEIGHT,
            initRoundPopulation = 80,
            useChache = true;
            WIDTH = document.documentElement.clientWidth;
            HEIGHT = document.documentElement.clientHeight;
            ctx.width = WIDTH;
            ctx.height = HEIGHT;
            
            function Round_item(index,x,y){
                this.index = index;
                this.x = x;
                this.y = y;
                this.useChache = useChache;
                this.cacheCanvas = document.createElement('canvas');
                this.cacheCtx = this.cacheCanvas.getContext('2d');
                this.cacheCtx.width = 6*this.r;
                this.cacheCtx.height = 6*this.r;
                this.r = Math.random()*2 +1;
                var alpha = (Math.floor(Math.random()*10)+1)/10/2;
                this.color = "rgba(255,255,255,"+alpha+")";
                if(useChache){
                    this.cache()
                }
            }
            Round_item.prototype.draw = function(){
                if(!useChache){
                    content.fillStyle = this.color;
                    content.shadowBlur = this.r*2;
                    content.beginPath();
                    content.arc(this.x,this.y,this.r,0,2*Math.PI,false);
                    content.closePath()
                    content.fill()
                }else{
                    content.drawImage(this.cacheCanvas,this.x-this.r,this.y - this.r)
                }
            }
            Round_item.prototype.cache = function(){
                this.cacheCtx.save();
                this.cacheCtx.fillStyle = this.color;
                this.cacheCtx.shadowColor = "white";
                this.cacheCtx.shadowBlur = this.r*2;
                this.cacheCtx.beginPath();
                this.cacheCtx.arc(this.r*3,this.r*3,this.r,0,2*Math.PI);
                this.cacheCtx.closePath();
                this.cacheCtx.fill();
                this.cacheCtx.restore()
            };
            function animate(){
                content.clearRect(0,0,WIDTH,HEIGHT);
                for(var i in round){
                    round[i].move()
                }
                requestAnimationFrame(animate)
            };
            Round_item.prototype.move = function(){
                this.y -= 0.15;
                if(this.y <= -10){
                    this.y = HEIGHT + 10
                }
                this.draw()
            }
            function init(){
                for(var i = 0; i < initRoundPopulation;i++){
                    round[i] = new Round_item(i,Math.random()*WIDTH,Math.random()*HEIGHT);
                    round[i].draw()
                }
                animate()
            }
            init()
    </script>
</body>
</html>
阅读 392

菜鸟杨@
积极向上的程序员

Talk is cheap, show the code!!

417 声望
831 粉丝
0 条评论
你知道吗?

Talk is cheap, show the code!!

417 声望
831 粉丝
宣传栏