html5 canvas 绘图

215 阅读5分钟

前端canvas 库

chart.js 图表 phaser features 动画游戏库

基础

注意: 先是canvas属性的宽高,再根据 css 定义的宽高进行(宽高)缩放(缩放到 css 宽高大小,因此可能会变形)

var canvas=document.getElementById('mycanvas')
var ctx=canvas.getContext('2d')
ctx.moveTo(0,0)
ctx.lineTo(100,100)
ctx.stroke()

一些 api

//路径
ctx.moveTo()
ctx.lineTo() //画直线
ctx.arc(300,300,radius,0,2*Math.PI,true) //画圆
ctx.rect()

// 样式
ctx.lineWidth=10
ctx.strokeStyle='#aaa'
ctx.fillStyle='rgba(0,244,244,1)'

// 填充和描边
ctx.stoke() //描边(要避免重复绘制)
ctx.fill() //填充
ctx.strokeRect(1,2,3,4) //画矩形
ctx.fillRect(1,2,3,4)

// 路径开始和闭合
ctx.beginPath() //重新开始一条路径
ctx.closePath() //闭合路径(路径不会断开)

//坐标系原点操作(针对坐标系变换)
ctx.translate(x,y) //平移
ctx.rotate(Math.PI/4) //旋转(顺时针)
ctx.scale(x,y)//当前坐标系进行 x,y 缩放

//保存上下文环境栈(样式和坐标转换)
ctx.save() //保存上面的上下文环境,入栈
ctx.restore() // 上下文环境出栈

// 渐变
//线性渐变
linearGradient= ctx.createLinearGradient(x1,y1,x2,y2)
linearGradient.addColorStop(0,'rgb(255,0,0)')
linearGradient.addColorStop(0.5,'rgb(255,0,255)')
ctx.fillStyle=linearGradient
ctx.fillRect(x1,y1,x2,y2)
//径向渐变(两个圆半径圈都是分界点)
radiaGradient=ctx.createRadialGradient(x1,y1,raidus,x2,y2,radius)
radiaGradient.addColorStop(0,'#ccc')

// 文字
ctx.font='50px 字体'
ctx.textBaseline='top' / top middle bottom
ctx.textAlign='center' //设置文字坐标是基于什么,默认时左
ctx.fillText(str,x,y)
ctx.strokeText(str,x,y)
ctx.measureText(str).width  //获取文本的宽度

// 图片绘制
var img=new Image()
img.src=''
img.onload=function(){
    ctx.drawImage(img,x1,y1)
    ctx.drawImage(img,x1,y1,width,height) //坐标和缩放
    // 参数解释(img,原图起始x,原图起始 y,原图截取的宽,原图截取的高,画布上的 x,画布上的 y,画布上缩放的 width,画布上缩放的 height)
    ctx.drawImage(img,sx1,sy1,swidth,sheight,x2,y2,width,height)
}

//图形画刷
var img=new img()
img.src=''
img.onload=function(){
    //四中模式 no-repeat ,repeat-x ,repeat-y ,repeat
    pattern = ctx.createPattern(img,'repeat') 
    ctx.fillStyle=pattern
}

// canvas 剪辑区域(配合 save 和 restore 来处理)
ctx.clip() //区域剪辑,只能在之前设置的路径中进行填充和描边

// 阴影(也可以是对文字)
ctx.shadowOffsetX=10
ctx.shadowOffsetY=10
ctx.shadowColor=''
ctx.shadowBlur=10 //模糊程度
ctx.fillStyle=''

// 曲线绘制
//圆弧
ctx.arc(x,y,radius,起始角度,结束角度,true(逆时针))
//贝塞尔曲线(三个点)
ctx.moveTo(x1,y1)
ctx.quadraticCurveTo(x2,y2,x3,y3) 
//二次贝塞尔曲线(四个点)
ctx.moveTo(x1,y1)
ctx.bezierCurveTo(x2,y2,x3,y3,x4,y4)

//清空画布
ctx.clearRect()

// 动画(一帧帧的合成,改变变量画好每一帧)
// 交互,改变动画变量和控制变量(将动画绘制变量抽离出来单独处理,而不是停止定时器之类)
canvas.onmousemove=function(e){
    mouseX=e.offsetX
    mouseY=e.offsetY
    if(判断条件){
        isActive=false
    }else{
        isActive=true
    }
}
setInterveal(function(){ 
    if(isActive){
         ctx.clearRect(0,0,canvas.width,canvas.height) //清空画布
         //画一帧
    }
   
})

// 离屏(无需在界面中显示)canvas技术
(1) 绘制 canvas1画布的图像(很复杂)
canvas1=document.getElementById('canvas1')
canvas1.getContext('2d') 
(2) 在 canvas2中把 cavas1作为图片绘制(作为背景或其他,很复杂的 canvas1绘制图形操作就只执行一次,其他都是绘制整个已有的 canvas1图片)
ctx.drawImage(canvas1,0,0,canvas1.width,canvas1.height,0,0,canvas.width,canvas.height)

canvas 绘制手势解锁

(function(w){
    /**
     * 实现画圆和划线:
     * 1、添加事件touchstart、touchmove、touchend
     * 2、touchstart判断是否点击的位置处于圆内getPosition,处于则初始化
     * lastpoint、restPoint
     * 3、touchmove做的就是:画圆drawPoint和画线drawLine
     *
     * 实现自动画圆的效果
     * 1、检测手势移动的位置是否处于圆内
     * 2、圆内的话则画圆 drawPoint
     * 3、已经画过实心圆的圆,无需重复检测
     *
     * 实现解锁成功:
     * 1、检测路径是否是对的
     * 2、如果是对的就重置,圆圈变绿
     * 3、不对也重置,圆圈变红
     * 4、重置
     */
    
    w.canvasLock=function(obj){
        this.height=obj.height
        this.width=obj.width
        this.chooseType=obj.chooseType
    }
    // js 方式动态生成 dom
    canvasLock.prototype.initDom=function(){
        var wrap=document.createElement('div')
        var str=`<h4 id="title" class="title">绘制解锁图案</h4>`
        wrap.setAttribute('style','position: absolute;top:0;left:0;right:0;bottom:0;');
        
        var canvas = document.createElement('canvas');
        canvas.setAttribute('id','canvas');
        canvas.style.cssText = 'background-color: #305066;display: inline-block;margin-top: 15px;';
        
        wrap.innerHTML = str;
        wrap.appendChild(canvas);

        var width = this.width || 300;
        var height = this.height || 300;

        document.body.appendChild(wrap);
        
        // 高清屏锁放
        canvas.style.width = width + "px";
        canvas.style.height = height + "px";

        canvas.width = width;
        canvas.height = height;
    }
    // 画圆
    canvas.prototype.drawCle=function(x,y){
        this.ctx.strokeStyle = '#CFE6FF';
        this.ctx.lineWidth = 2;
        this.ctx.beginPath();
        this.ctx.arc(x, y, this.r, 0, Math.PI * 2, true);
        this.ctx.closePath();
        this.ctx.stroke();
    }
    // 创建解锁点的坐标,根据canvas的大小来平均分配半径
    canvasLock.prototype.createCircle = function() {
        var n = this.chooseType;
        var count = 0;
        this.r = this.ctx.canvas.width / (2 + 4 * n);// 公式计算
        this.lastPoint = [];
        this.arr = [];
        this.restPoint = [];
        var r = this.r;
        for (var i = 0 ; i < n ; i++) {
            for (var j = 0 ; j < n ; j++) {
                count++;
                var obj = {
                    x: j * 4 * r + 3 * r,
                    y: i * 4 * r + 3 * r,
                    index: count
                };
                this.arr.push(obj);
                this.restPoint.push(obj);
            }
        }

        this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
        for (var i = 0 ; i < this.arr.length ; i++) {
            // 画圆函数
            this.drawCle(this.arr[i].x, this.arr[i].y);
        }
    }
    
    // 程序初始化
    canvasLock.prototype.init = function() {
        this.initDom();
        this.canvas = document.getElementById('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.touchFlag = false;
        // 1、确定半径
        // 2、确定每一个圆的中心坐标点
        // 3、一行3个圆14个半径,一行4个圆有18个半径
        this.createCircle();
        this.bindEvent();
    }
    
    canvasLock.prototype.bindEvent=function(){
        var self = this;
        this.canvas.addEventListener("touchstart", function (e) {
            //  touchstart判断是否点击的位置处于圆内getPosition,处于则初始化
            //  lastpoint、restPoint

            // po有x和y,并且是相较于canvas边距
            var po = self.getPosition(e);
            // 判断是否在圆内的原理:多出来的这条 x/y < r 在圆内
            for (var i = 0 ; i < self.arr.length ; i++) {
                if (Math.abs(po.x - self.arr[i].x) < self.r && Math.abs(po.y - self.arr[i].y) < self.r) {
                    self.touchFlag = true;
                    // lastPoint存放的就是选中的圆圈的x/y坐标值
                    self.lastPoint.push(self.arr[i]);
                    self.restPoint.splice(i,1);
                    break;
                }
            }
        }, false);
        
        // 每touchmove 一次,就清空画布绘制一次
        this.canvas.addEventListener("touchmove", function (e) {
               // touchmove做的就是:画圆drawPoint和划线drawLine
               if (self.touchFlag) {
                  self.update(self.getPosition(e));
               }
            }, false);
            
        // touchend 检测
         this.canvas.addEventListener("touchend", function(e){
                if (self.touchFlag) {
                    self.storePass(self.lastPoint);
                    setTimeout(function(){
                       self.reset();
                   }, 300);
                }
            }, false);
    }
    
    // 获取touch点相对于canvas的坐标
    canvasLock.prototype.getPosition = function(e) {
        var rect = e.currentTarget.getBoundingClientRect();
        var po = {
            x: (e.touches[0].clientX - rect.left),
            y: (e.touches[0].clientY - rect.top)
        };
        return po;
    }
    // 核心变换方法在touchmove时候调用
    canvasLock.prototype.update = function(po) {
        this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
        // 重新画9个圆圈
        for (var i = 0 ; i < this.arr.length ; i++) { // 每帧先把面板画出来
            this.drawCle(this.arr[i].x, this.arr[i].y);
        }
        // 1、检测手势移动的位置是否处于下一个圆内
        // 2、圆内的话则画圆 drawPoint
        // 3、已经画过实心圆的圆,无需重复检测
        for (var i = 0 ; i < this.restPoint.length ; i++) {
            if (Math.abs(po.x - this.restPoint[i].x) < this.r && Math.abs(po.y - this.restPoint[i].y) < this.r) {
                this.lastPoint.push(this.restPoint[i]);
                this.restPoint.splice(i, 1);
                break;
            }
        }
        
        this.drawPoint();// 画圆
        this.drawLine(po);// 画线
    }
    
    // 初始化圆心 
    canvasLock.prototype.drawPoint = function() { 
        for (var i = 0 ; i < this.lastPoint.length ; i++) {
            this.ctx.fillStyle = '#CFE6FF';
            this.ctx.beginPath();
            this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r / 2, 0, Math.PI * 2, true);
            this.ctx.closePath();
            this.ctx.fill();
        }
    }
    // 画线
    canvasLock.prototype.drawLine = function(po) {
        this.ctx.beginPath();
        this.ctx.lineWidth = 3;
        this.ctx.moveTo(this.lastPoint[0].x, this.lastPoint[0].y);
        for (var i = 1 ; i < this.lastPoint.length ; i++) {
            this.ctx.lineTo(this.lastPoint[i].x, this.lastPoint[i].y);
        }
        this.ctx.lineTo(po.x, po.y);
        this.ctx.stroke();
        this.ctx.closePath();
    }
    
    // 1、检测路径是否是对的
    // 2、如果是对的就重置,圆圈变绿
    // 3、不对也重置,圆圈变红
    // 4、重置
    canvasLock.prototype.storePass = function() {
        if (this.checkPass()) {
            document.getElementById('title').innerHTML = '解锁成功';
            this.drawStatusPoint('#2CFF26');
        }else{
            document.getElementById('title').innerHTML = '解锁失败';
            this.drawStatusPoint('red');
        }
    }
    canvasLock.prototype.checkPass = function() {
        var p1 = '123',
        p2 = '';
        for (var i = 0 ; i < this.lastPoint.length ; i++) {
            p2 += this.lastPoint[i].index;
        }
        return p1 === p2;
    }
    canvasLock.prototype.drawStatusPoint = function(type) {
        for (var i = 0 ; i < this.lastPoint.length ; i++) {
            this.ctx.strokeStyle = type;
            this.ctx.beginPath();
            this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r, 0, Math.PI * 2, true);
            this.ctx.closePath();
            this.ctx.stroke();
        }
    }
    canvasLock.prototype.reset = function(){
        this.createCircle();
    }
}(window))