canvas的简单介绍和快速上手

简介

<canvas>HTML5 新增的元素,可用于通过使用JavaScript中的脚本来绘制图形。例如,它可以用于绘制图形,制作照片,创建动画,甚至可以进行实时视频处理或渲染。

Canvas 与 SVG 的比较

下表列出了 canvas 与 SVG 之间的一些不同之处。

Canvas

  • 依赖分辨率

  • 不支持事件处理器

  • 弱的文本渲染能力

  • 能够以 .png 或 .jpg 格式保存结果图像

  • 最适合图像密集型的游戏,其中的许多对象会被频繁重绘

SVG

  • 不依赖分辨率

  • 支持事件处理器

  • 最适合带有大型渲染区域的应用程序(比如谷歌地图)

  • 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)

  • 不适合游戏应用

<canvas> 看起来和 <img> 元素很相像,唯一的不同就是它并没有 src 和 alt 属性。实际上,<canvas> 标签只有两个属性—— widthheight。这些都是可选的,并且同样利用 DOMproperties 来设置。当没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素。

该元素可以使用CSS来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果CSS的尺寸与初始画布的比例不一致,它会出现扭曲。

注意: 如果你绘制出来的图像是扭曲的, 尝试用width和height属性为<canvas>明确规定宽高,而不是使用CSS,画面扭曲或者模糊一般会出现在移动端,pc端一般没有影响,如果移动端出现锯齿,下面是一个解决移动端锯齿的方法。

// 去除锯齿

let canvas = document.querySelector('#gauge')

const width = canvas.width,height=canvas.height;

const cxt = canvas.getContext('2d')

if (window.devicePixelRatio) {

  canvas.style.width = width + "px";

  canvas.style.height = height + "px";

  canvas.height = height * window.devicePixelRatio;

  canvas.width = width * window.devicePixelRatio;

  cxt.scale(window.devicePixelRatio, window.devicePixelRatio);

};

用法示例(三个实例上手canvas):

  • Demo1用canvas画一个电子时钟:

效果展示:

image.png
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <canvas class='clock'></canvas>
</body>
<script>
    function drawClock(selector,option) {
        this.box = document.querySelector(selector); //获取目标元素canvas
        this.bgcolor=option.bgcolor||'#e8e7e3';  // 默认配置背景色
        this.pointercolor=option.pointercolor||"#b1b71e"; // 默认配置指针颜色
        this.pinColor = option.pinColor || '#e5a786'// 默认及刻度颜色
        this.isShowNumber=option.isShowNumber||false // 是否显示数字时钟
        this.size=option.size||300 // 画布大小
        this.ctx = this.box.getContext('2d') // 获取2d画笔
        this.box.width = this.size //设置宽高
        this.box.height = this.size
        this.font = option.font || '48px serif'//设置默认字体
        this.linStyle = option.lineStyle || 'round'//设置线的样式
        this.minutePinWidth = option.minutePinWidth || 3 //设置指针宽度
        this.hourPinWidth = option.hourPinWidth || 6
        this.init() // 初始化
    }
    drawClock.prototype = {
        init(){
            let that = this
            this.tickTock(that) //让指针动起来
        },
        safeDraw(callback){  //每次都重新重制画笔不影响下一次画其他东西
            this.ctx.save() // 保存上次的画笔信息
            this.ctx.beginPath(); // 从新开始
            callback.call(this) // this改变this指向
            this.ctx.closePath(); // 关闭路径
            this.ctx.restore() // 重制画笔
        },
        drawBg(){ // 绘制背景图
            this.safeDraw(
                function(){
                    this.ctx.fillStyle = this.bgcolor  //设置填充颜色
                    this.ctx.arc(this.size/2, this.size/2, this.size/2, 0, Math.PI*2, true) //绘制圆形 。@params(圆心x,圆心y,起始弧度,结束弧度,是否是顺时针)
                    this.ctx.fill()//填充颜色
                }
            )
        },
        drawTick() {  // 绘制刻度   
            let length = 10 
            for(let i = 0; i < 60 ;i ++) {
                if (i%5==0) { //每五个一个长的刻度
                    length = 20
                } else {
                    length = 10
                }
                this.safeDraw(
                    function() {
                        this.ctx.strokeStyle = this.pointercolor // 设置描边的颜色
                        this.ctx.translate(this.size/2,this.size/2) // 将坐标轴的起点移动到圆心   @important  移动和旋转坐标轴原点需要提前
                        this.ctx.rotate(Math.PI/30*i)//然后依照弧度进行旋转
  
                         
                        this.ctx.moveTo(this.size/2-length,0)//画线先将起点移动到距边缘length的距离
                        this.ctx.lineTo(this.size/2,0)//再将length画过去
                        this.ctx.stroke()//进行描边
                    }
                )
            }
        },
        drawSecondPin() { // 绘制秒针
            let second =new Date().getSeconds()
            this.safeDraw(
                function() {
                    this.ctx.lineCap = this.linStyle
                    this.ctx.translate(this.size/2,this.size/2) // 
                    this.ctx.rotate(second*Math.PI/30-Math.PI/2)
                    this.ctx.moveTo(0,0)
                    this.ctx.lineTo(this.size/2-30,0)
                    this.ctx.strokeStyle=this.pointercolor
                    this.ctx.stroke()
                }
            )
        },
        tickTock(that) { // 让时间动起来
            setInterval(
                function() {
                    that.ctx.clearRect(0, 0, that.size, that.size);
                    that.drawBg()
                    that.drawTick()
                    that.drawSecondPin()
                    that.drawMinute()
                    that.drawHour()
                    that.drawNumTime()
                    // setTimeout(arguments.callee,1000)
                }, 1000
            )
        },
        drawMinute() {  // 绘制分针
            let minutes =new Date().getMinutes()
            this.safeDraw(
                function() {
                    this.ctx.lineCap = this.linStyle
                    this.ctx.lineWidth = this.minutePinWidth
                    this.ctx.translate(this.size/2,this.size/2)
                    this.ctx.rotate(minutes*Math.PI/30-Math.PI/2)
                    this.ctx.moveTo(0,0)
                    this.ctx.lineTo(this.size/2-50,0)
                    this.ctx.strokeStyle=this.pointercolor
                    this.ctx.stroke()
                }
            )
        },
        drawHour() { // 绘制时针
            let minutes = new Date().getMinutes()
            let hours = new Date().getHours()
            hoursNum = hours*60 + minutes
            this.safeDraw(
                function() {
                    this.ctx.lineCap = this.linStyle
                    this.ctx.lineWidth = this.hourPinWidth
                    this.ctx.translate(this.size/2,this.size/2) // 
                    this.ctx.rotate(hoursNum*Math.PI/360-Math.PI/2)
                    this.ctx.moveTo(0,0)
                    this.ctx.lineTo(this.size/2-100,0)
                    this.ctx.strokeStyle=this.pointercolor
                    this.ctx.stroke()
                }
            )
        },
        drawNumTime() { // 绘制时间 
            let time = new Date().toTimeString().split(' ')[0]
            this.ctx.font =  this.font;
            this.ctx.fillStyle = this.pointercolor
            let textWidth = this.ctx.measureText(time).width //获取字体宽度
            this.ctx.fillText(time, this.size/2-textWidth/2,this.size/2-40); // @params(文本,文本的位置x,文本的位置y)
        }
    }
    new drawClock('.clock',{})
</script>
</html>

|

  • Demo2用canvas实现一个仪表盘:
import './Gauge.less'


export default {

  name: 'Gauge',

  props: ['data'],

  data() {

    return {

      board: null,

      title: '',

    }

  },

  mounted() {

    if (this.data) { // 检测父元素有没有传过来数据

      let num = 0

      const item = this.data

      this.title = item.name

      let val = item.data.default[0].value

      num = parseFloat(val)  // 处理数据

      this.draw(num)

    }

  },

  methods: {

    draw(percent, sR) {  // 画仪表盘的逻辑

      if (sR < Math.PI / 2 || sR >= 3 / 2 * Math.PI) {

        return;

      };

      // 去除锯齿

      let canvas = document.querySelector('#gauge')

      const width = canvas.width,height=canvas.height;

      const cxt = canvas.getContext('2d')

      if (window.devicePixelRatio) {

        canvas.style.width = width + "px";

        canvas.style.height = height + "px";

        canvas.height = height * window.devicePixelRatio;

        canvas.width = width * window.devicePixelRatio;

        cxt.scale(window.devicePixelRatio, window.devicePixelRatio);

      };

      // 初始化配置项

      let cWidth = width;

      let cHeight = height;

      let baseColor = '#f2f3f6';

      let coverColor = '#2b73f9';

      let fontColor = '#78849A';

      let fontFamily = '36px DINCondensed-Bold';

      let ajustHeight = 20;

      let radius = 65;

      let lineWidth = 12;

      let PI = Math.PI;

      sR = sR || 7 / 8 * PI; // 默认圆弧的起始点弧度为7/8

      const finalRadian = sR + ((PI + (PI - sR) * 2) * percent / 100); // 小圆圈的终点弧度

      const step = (PI + (PI - sR) * 2) / 100; // 一个1%对应的弧度大小

      let text = 0; // 显示的数字

      let num = 0; // 仪表盘进度

      // 添加动画效果

      window.requestAnimationFrame(paint);

      function paint() { // 绘制图样

        cxt.clearRect(0, 0, cWidth, cHeight); // 每次绘制都先清空画布

        let endRadian = sR + num * step;

        // 画灰色圆弧

        drawCanvas(cWidth / 2, cHeight / 2 + ajustHeight, radius, sR, sR + (PI + (PI - sR) * 2), baseColor, lineWidth);

        // 画红色圆弧

        drawCanvas(cWidth / 2, cHeight / 2 + ajustHeight, radius, sR, endRadian, coverColor, lineWidth);

        // 小圆头

        // 小圆头其实就是一个圆,关键的是找到其圆心

        let angle = 2 * PI - endRadian; // 转换成逆时针方向的弧度(三角函数中的)

        let xPos = Math.cos(angle) * radius + cWidth / 2; // 小圆 圆心的x坐标

        let yPos = -Math.sin(angle) * radius + cHeight / 2 + ajustHeight; // 小圆 圆心的y坐标

        fillCanvas(xPos, yPos, 3, 0, 2 * PI, baseColor, 1);

        // 填充数字

        cxt.fillStyle = fontColor; // 初始化填充样式

        cxt.font = fontFamily;

        let textWidth = cxt.measureText(text + '%').width;

        cxt.fillText(text + '%', cWidth / 2 - textWidth / 2, cHeight / 2 + ajustHeight + lineWidth);

        num++;

        text++;

        if (endRadian.toFixed(2) <= finalRadian.toFixed(2)) {

          window.requestAnimationFrame(paint);

        }

        if (num >= percent) { //  小数和超出范围处理

          text = percent

          return

        }

        if (num >= 100) { //  小数和超出范围处理

          num = 100

          text = percent

          return

        }

        if (percent <= 0) { //  小数和超出范围处理

          num = 0

          text = percent

          return

        }

      }

      function drawCanvas(x, y, r, sRadian, eRadian, color, lineWidth) { // 描边轮廓绘图

        cxt.beginPath();

        cxt.lineCap = "round";

        cxt.strokeStyle = color;

        cxt.lineWidth = lineWidth;

        cxt.arc(x, y, r, sRadian, eRadian, false);

        cxt.stroke();

      }

      function fillCanvas(x, y, r, sRadian, eRadian, color, lineWidth) { // 填充内容绘图

        cxt.beginPath();

        cxt.lineCap = "round";

        cxt.fillStyle = color;

        cxt.lineWidth = lineWidth;

        cxt.arc(x, y, r, sRadian, eRadian, false);

        cxt.fill();

      }

    }

  },

  render() {

    return <div class="NewGuageBox">

      <div class="gauge_title">

        <span class="title" onClick={() => {

          this.HelpInfo(this.data['help'].content.default)

        }}>{this.title}<i class="iconfont icon-info"></i></span>

      </div>

      <div class="gauge_box">

        <canvas id="gauge" width="300" height="150"></canvas>

      </div>

    </div>;

  },

};

|

demo3 用canvas实现这样的效果:

image.png
import Charts from './charts1'

render() {  // 父级元素的数据

   let config = [{

       class:'canvas',

       color:'#ea6e5a',

       value:'20',

       title:'20',

       label:'业务占比'

   },{

       class:'canvas1',

       color:'',

       value:'80',

       title:'80',

       label:'业务占比'

   },{

       class:'canvas2',

       color:'#c89efe',

       value:'90',

       title:'90',

       label:'上架量'

   }]

   return <div className="wrap">{

       config.map((item,index)=>{

           return  <Charts width='600' height='100' data={item} key={index}></Charts>

       })

   }

   </div>

}

图表组件:

/**

 * Created by guoguangkun on 2017/9/19.

 */

import React, { Component } from 'react'

import { bindActionCreators } from 'redux'

import { connect } from 'react-redux'

import { hashHistory, Link } from 'react-router'

import { Spin, message, Form, Icon, Input, Button, Row, Col } from 'antd'

import { fetchLogin, userInfo } from 'actions/common'

const FormItem = Form.Item

@connect((state, props) => ({

    config: state.config,

    loginResponse: state.tabListResult,

}))

@Form.create({

    onFieldsChange(props, items) {

        // console.log(items)

        // props.cacheSearch(items);

    },

})

export default class Charts extends Component {

    // 初始化页面常量 绑定事件方法

    constructor(props, context) {

        super(props)

        this.state = {

            loading: false,

        }

        this.draw = this.draw.bind(this)

    }

    componentDidMount() {

        console.log(this.props.data)

        this.draw(this.props.data.value,300, '.' + this.props.data.class,this.props.data.color,this.props.data.value,this.props.data.label) // 此处第三个参数,选测器,一定要加前边的 点,因为这个点导致没选择上元素

    }

    draw = (percent, boxSize, target, targetColor, title, label ) => { // 画图的逻辑

        // 去除锯齿

        let canvas =document.querySelector(target)

        const width = canvas.width,height=canvas.height;

        const cxt = canvas.getContext('2d')

        if (window.devicePixelRatio) {

            canvas.style.width = width + "px";

            canvas.style.height = height + "px";

            canvas.height = height * window.devicePixelRatio;

            canvas.width = width * window.devicePixelRatio;

            cxt.scale(window.devicePixelRatio, window.devicePixelRatio);

        };

        // 初始化配置项

        let start = 160;

        let cWidth = width;

        let cHeight = height;

        let maxLength = 400;

        let baseColor =  '#e2ecfa';

        let coverColor = targetColor || '#2b73f9';

        let fontColor = '#78849A';

        let fontFamily = '24px DINCondensed-Bold';

        let ajustHeight = 20;

        let radius = 65;

        let lineWidth = 12;

        const finalRadian = maxLength * percent / 100; // 小圆圈的终点弧度

        const step = maxLength / 100; // 一个1%对应的弧度大小

        let text = 0; // 显示的数字

        let num = 0; // 仪表盘进度

        let PI = Math.PI

        let titleText = title || '60%'

        let labelText = label || '人均带看'

        // 添加动画效果

        window.requestAnimationFrame(paint);

        function paint() { // 绘制图样

            cxt.clearRect(0, 0, cWidth, cHeight); // 每次绘制都先清空画布

            let endRadian = start + num * step;

            // 底色

            drawCanvas(maxLength + start, 30, baseColor, lineWidth,start);

            // 进度

            drawCanvas(endRadian, 30, coverColor, lineWidth,start);

            // 小圆头

            // 小圆头其实就是一个圆,关键的是找到其圆心

            fillCanvas(endRadian, 30, 3, 0, 2 * PI, baseColor, 1);

            // 填充数字

            drawFonts('0',start,60,'#3d4049')

            drawFonts('20%',start+20*step,60,'#3d4049')

            drawFonts('80%',start+80*step,60,'#3d4049')

            drawFonts('100%',start+100*step,60,'#3d4049')

            //绘制标题和数值

            drawFonts(titleText,start-60,40,'#3d4049',fontFamily)

            drawFonts(labelText,start-60,60,'#8e9db2')

            num++;

            text++;

            if (num <= percent) {

                window.requestAnimationFrame(paint);

            }

            if (num >= percent) { //  小数和超出范围处理

                text = percent

                return

            }

            if (num >= 100) { //  小数和超出范围处理

                num = 100

                text = percent

                return

            }

            if (percent <= 0) { //  小数和超出范围处理

                num = 0

                text = percent

                return

            }

        }

        function drawCanvas(x, y, color, lineWidth,start) { // 描边轮廓绘图

            cxt.beginPath();

            cxt.lineCap = "round";

            cxt.strokeStyle = color;

            cxt.lineWidth = lineWidth;

            cxt.moveTo(start, 30);

            cxt.lineTo(x,y)

            cxt.stroke();

        }

        function fillCanvas(x, y, r, sRadian, eRadian, color, lineWidth) { // 填充内容绘图

            cxt.beginPath();

            cxt.lineCap = "round";

            cxt.fillStyle = color;

            cxt.lineWidth = lineWidth;

            cxt.arc(x, y, r, sRadian, eRadian, false);

            cxt.fill();

        }

        function drawFonts(text,x,y,fontColor,fontFamily) { //绘制文字

            cxt.beginPath();

            cxt.fillStyle = fontColor || '#e6eaed'; // 初始化填充样式

            cxt.font = fontFamily || '16px sans-serif';

            cxt.textAlign = 'right' // 设置文字对齐方向

            let textWidth = cxt.measureText(text).width;

            cxt.fillText(text, x, y);

        }

    }

    handleClick = () => {

        console.log(1)

    }

    noop = () => false

    render() {

        return <div className="wrap" onClick={this.handleClick}>

              <canvas className={this.props.data.class}width='600' height='100'></canvas>

        </div>

    }

}
/**

 * Created by guoguangkun on 2017/9/19.   all rights reserved 

 */

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,406评论 1 45
  • 学习html5时都会碰到svg和canvas,这两个都可以在浏览器中绘制图形,那么这两个有什么区别呢。下面对两者进...
    稻香Snowy阅读 3,288评论 0 5
  • 书中代码示例效果展示:Core HTML5 Canvas Examples 基础知识 canvas元素 canva...
    szu_bee阅读 2,798评论 2 28
  • 看了笑来老师专栏分享的一篇成长记录,有点启发。那位同学才订阅专栏一个多月,不但看完了所有的内容(共6个多月),而且...
    刘国涛耐火阅读 203评论 0 1
  • 一,今天用读数利器已经完全达到每个数字一秒之内,开始尝试数字闪读。虽然读数的时候已经达到一秒之内,但刚开始闪现的时...
    李姗珊阅读 174评论 0 0