当我们讨论 Canvas 时,我们在讨论什么

825 阅读4分钟

本文作者:毕老铁

前言

Web API 接口参考定义

Canvas API 提供了一个通过 JavaScriptHTMLCanvas 元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。 Canvas API 主要聚焦于 2D 图形。

相比较 CSSanimation 动画,Canvas 更加适用于复杂非线性的动画,单从实现成本上来说,线性的简单动画用 animation 更加方便可维护。

Canvas 在活动大小促、后台数据可视化展示、自定义图片编辑器以及自定义视频播放器这些业务场景中是很有用武之地的。

搓搓小手,预热一下

HTML 标准的其他标签一样书写方式:

<canvas id="my-canvas" width="116" height="116"></canvas>

比较特殊的是: Canvas 只有两个属性 widthheight,当没有设置宽高的时候,会初始化宽度为 300px 和高度为 150px。设置宽高的情况下,有小数会默认省略小数点,例如 116.666px,实际会被渲染为 116 px,带上单位的话会被自动省略,例如 116 em,实际会被渲染为 116 px。

<canvas id="my-canvas" width="116.66em" height="116.66"></canvas>

avater

撸起袖子加油干

双十一又要到了,前端同学免不了要实现各种酷炫的动效,莫慌,我们可以放心地把 2d 部分交给 Canvas 元素。

动效部分

配合 APNG 图片资源 + Canvas 的组合,轻松地实现 2d 图形复杂动画,关于 Apng 的实现原理可以看网易云音乐团队的 Web 端 APNG 播放实现原理

在设计同学提供好好对应的 APNG 素材之后,通过下面的代码就可以实现一段动画:

// arrayBuffer 为 APNG 通过 XMLHttpRequest 下载
const getBufferFromXhr = function (url) {
    return new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.responseType = 'arraybuffer';

        xhr.onload = function() {
            let arrayBuffer = xhr.response;
            resolve(arrayBuffer);
        };
        xhr.onerror = function() {
            reject(new Error('请求图片资源失败'));
        };
        xhr.send();
    });
};
// APNG 图片地址
let url = '你的图片地址';
let player = null;
let arrayBuffer = getBufferFromXhr(url);
let apng = parseAPNG(arrayBuffer);
const canvas = document.getElementById('my-canvas');
apng.getPlayer(canvas.getContext('2d')).then(p => {
    player = p;
    // 播放速率
    player.playbackRate = 1;
    // 播放次数 0 为无限播放
    // player._apng.numPlays = 0;
    // 监听当前动画播放到第几帧,frameNum 为帧数值
    player.emit = function(event, frameNum) {
        if (frameNum === 13) {
          // todo....
        }
    };
    player.play();
});

APNG

静态展示和图片编辑

组合使用 CanvasdrawImagearcclipfillTexttoDataURL API 来实现:

const canvas = document.getElementById('my-canvas');
const context = canvas.getContext('2d');
const image = document.getElementById('my-image');
// 在 x:0、y:0 的起始点,画上一张图片
context.drawImage(image, 0, 0, 350, 430);
// 在 x:100、y:100 的位置,画一个半径为 20 的满圆,并进行裁剪
context.arc(100, 100, 20, 0, 2 * Math.PI);
context.clip();
// 在规定好文本样式,并在指定的位置写入
context.font = '500 12px PingFangSC-Medium';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillStyle = '#F2266D';
context.fillText('悲伤的猪猪', 100, 100);
// 如果需要获取到被编辑 canvas 元素的 data URI
console(canvas.toDataURL());

砍断路上的小荆棘

  • 需要使用 getImageDatatoDataURL API 会出现跨域问题,需要设置图片的跨域属性。
img.crossOrigin = '';

否则就会出现下面的报错: avater avater

  • 绘制图片、文字的时候会出现模糊:

Canvas 不是矢量图,而是位图模式的。高 dpi 显示设备意味着每平方英寸有更多倍数的像素。以二倍屏为例子, Canvas 在 Retina 屏幕下占据了 2 倍的空间,相当于图片和文字放大了一倍,所以绘制出来的图片文字等会变模糊会看起来模糊。 从这个原理着手,可以轻松地解决这个问题:

// 当我要绘制一张 150 * 150 图片到 canvas 元素中,需要设计配合给出二倍或者三倍图,这样子可以保证绘制后不会模糊
let devRatio = window.devicePixelRatio || 1,
    backingStore = ctx.backingStorePixelRatio ||
    ctx.webkitBackingStorePixelRatio ||
    ctx.mozBackingStorePixelRatio ||
    ctx.msBackingStorePixelRatio ||
    ctx.oBackingStorePixelRatio ||
    ctx.backingStorePixelRatio || 1;
const ratio = devRatio / backingStore;
// canvas放大像素比倍
canvasEle.setAttribute('width', winW * ratio);  
canvasEle.setAttribute('height', winH * ratio);
// canvas 放大后,相应的绘制图片也要放大
canvasEle.drawImage(imageEle, 0, 0, 150 * ratio, 150 * ratio);
  • Canvas 的属性宽高和样式宽高是不一样的:属性宽高指的是该 Canvas 元素的画布大小,样式宽高是指元素屏幕占位上的宽高大小。如下面例子所示:我们可以画出一个画布大小为 300px * 300px ,空间占位为 150px * 150px。
<canvas id="mine-canvas" width="300" height="300" style="width:150px;height:150px"></canvas>

唱着小歌,迈过小山坡

Canvas 兼容性 Canvas 上面是我对 Canvas 一些零散知识点的总结,希望对大家有所帮助。如有表达不当之处,还请指正。

参考