canvas[学习笔记五]

200 阅读1分钟

扩展和优化

1、图像平滑

<script>
    const canvas=document.getElementById('canvas');
    canvas.width=window.innerWidth;
    canvas.height=window.innerHeight;
    const ctx=canvas.getContext('2d');
    /*图像平滑 imageSmoothingEnabled*/


    /*图像源*/
    const img=new Image();
    img.src='./images/chess.jpg';
    img.onload=function(){
        const {width,height}=img;
        /*绘图+移动 drawImage(image, x, y) */
        ctx.drawImage(img,100,100,width*10,height*10);
        ctx.imageSmoothingEnabled=false;
        ctx.drawImage(img,100,100+height*10,width*10,height*10);
    };

</script>

图像平滑.jpg

2、canvas 画出一像素宽的线

<script>
    const canvas=document.getElementById('canvas');
    canvas.width=window.innerWidth;
    canvas.height=window.innerHeight;
    const ctx=canvas.getContext('2d');

    /*绘制直线*/
    ctx.beginPath();
    ctx.lineWidth=1;
    ctx.moveTo(50,50);
    ctx.lineTo(400,50);
    ctx.stroke();

    /*一像素宽的线*/
    /*方法一*/
    ctx.beginPath();
    ctx.lineWidth=1;
    ctx.moveTo(50,150.5);
    ctx.lineTo(400,150.5);
    ctx.stroke();
    /*方法二*/
    ctx.translate(0.5,0.5);
    ctx.beginPath();
    ctx.lineWidth=1;
    ctx.moveTo(50,250);
    ctx.lineTo(400,250);
    ctx.stroke();

</script>

3、适配设备的像素比

<script>
/*适配设备分辨率*/
    {
        const canvas=document.getElementById('canvas2');
        const  ctx=canvas.getContext('2d');
        //通过window 对象获取设备像素比devicePixelRatio
        const ratio=window.devicePixelRatio;
        console.log('ratio',ratio);

        //让canvas 的画布尺寸乘以像素比,即将canvas 画布变大
        canvas.width=width*ratio;
        canvas.height=height*ratio;

        //让canvas 坐标系的缩放和画布的缩放同步
        ctx.scale(ratio,ratio);

        //设置canvas css尺寸,定义画布的视觉大小
        canvas.style.width=width+'px';
        canvas.style.height=height+'px';

        //绘图
        ctx.font='60px arial ';
        ctx.fillText('canvas',10,100);
    }
</script>

4、canvas转图片

<body>
<canvas id="canvas" width="420" height="126"></canvas>
<img id="img" src="" alt="">
<script>
    const canvas=document.getElementById('canvas');
    const ctx=canvas.getContext('2d');

    const img=new Image();
    img.src='https://img.kaikeba.com/70350130700202jusm.png';
    img.setAttribute("crossOrigin",'Anonymous');

    img.onload=function(){
        ctx.drawImage(img,0,0);
        const imgURL=canvas.toDataURL();
        console.log('imgURL',imgURL);
        document.querySelector('#img').setAttribute('src',imgURL);
    }
</script>
</body>

5、小球拖尾

<script>
    const canvas=document.getElementById('canvas');
    const [width,height]=[window.innerWidth,window.innerHeight];
    canvas.width=width;
    canvas.height=height;
    //画笔
    const  ctx=canvas.getContext('2d');

    //小球对象化
    class Ball{
        constructor(r,color='#00acec'){
            this.color=color;
            this.r=r;
            this.x=0;
            this.y=0;
        }
        draw(ctx){
            ctx.save();
            ctx.beginPath();
            ctx.fillStyle=this.color;
            ctx.arc(this.x,this.y,this.r,0,Math.PI*2);
            ctx.fill();
            ctx.restore();
        }
    }

    //实例化小球
    let ball=new Ball(15);
    ball.y=50;
    ball.x=width/2;

    //记录时间 time
    let time=new Date();

    //重力 gravity
    const gravity=0.01;

    //弹力
    const bounding=-0.8;

    //速度/毫秒 vy
    let vy=0.3;
    let vx=0.3;

    //动画方法
    function animate(){
        //时间差
        let now=new Date();
        let diff=now-time;
        time=now;

        //加速度
        vy+=gravity;

        //设置小球位置
        ball.y+=vy*diff;
        ball.x+=vx*diff;

        //底部碰撞
        if(ball.y+ball.r>height){
            ball.y=height-ball.r;
            vy*=bounding;
        }
        //左侧碰撞
        if(ball.x-ball.r<0){
            ball.x=ball.r;
            vx*=bounding;
        }
        //右侧碰撞
        if(ball.x+ball.r>width){
            ball.x=width-ball.r;
            vx*=bounding;
        }

    }

    //渲染方法
    !(function render(){
        //设置动画
        animate();
        //擦除
        // ctx.clearRect(0,0,width,height);
        // 每一次绘制都会给前面的叠加一层类似滤镜的遮罩
        ctx.fillStyle='rgba(250,235,215,0.1)';
        ctx.fillRect(0,0,width,height);
        //绘图
        ball.draw(ctx);
        //递归调用render 函数
        window.requestAnimationFrame(render);
    })()
</script>

小球拖尾.gif

6、指向鼠标的线三维

// 建立二维向量 js 文件
// Vector2.js
export default class Vector2{
    constructor(x=0,y=0){
        this.x=x;
        this.y=y;
    }
    //加法 add(v)
    add(v){
        this.x+=v.x;
        this.y+=v.y;
        return this;
    }

    //减法 sub(v)
    sub(v){
        this.x-=v.x;
        this.y-=v.y;
        return this;
    }

    //角度的读取 angle()
    angle(){
        const {x,y}=this;
        let dir=Math.atan2(y,x);
        if(dir<0){dir+=Math.PI*2}
        return dir;
    }

    //旋转 rotate(a)
    rotate(a){
        const c=Math.cos(a);
        const s=Math.sin(a);
        const {x,y}=this;
        this.x=x*c-y*s;
        this.y=x*s+y*c;
        return this;
    }

    //长度读取 length()
    length(){
        const {x,y}=this;
        return Math.sqrt(x*x+y*y);
    }

    //设置长度 setLength(len)
    setLength(len){
        const {x,y}=this;
        const r=this.length();
        const c=x/r;
        const s=y/r;
        this.x=c*len;
        this.y=s*len;
        return this;
    }

    //克隆 clone()
    clone(){
        return new Vector2(this.x,this.y);
    }

    //拷贝 copy(v)
    copy(v){
        this.x=v.x;
        this.y=v.y;
        return this;
    }

    //极坐标 polar(len长度,dir方向)
    static polar(len,dir){
        return new Vector2(
            Math.cos(dir)*len,
            Math.sin(dir)*len
        )
    }
}
<script type="module">
    import Vector2 from "./jsm/Vector2.js";

    const [width,height]=[window.innerWidth,window.innerHeight];
    const canvas=document.getElementById('canvas');
    canvas.width=width;
    canvas.height=height;
    const  ctx=canvas.getContext('2d');

    //线对象
    class Line{
        constructor(start=new Vector2(),end=new Vector2()) {
            this.start=start;
            this.end=end;
        }
        draw(ctx){
            const {start,end}=this;
            ctx.save();
            //线
            ctx.beginPath();
            ctx.moveTo(start.x,start.y);
            ctx.lineTo(end.x,end.y);
            ctx.lineWidth=2;
            ctx.stroke();
            //圆点
            ctx.beginPath();
            ctx.arc(end.x,end.y,6,0,Math.PI*2);
            ctx.fill();
            ctx.restore();
        }
    }
    //圆圈对象
    class Circle{
        constructor(pos,r) {
            this.pos=pos;
            this.r=r;
        }
        draw(ctx){
            const {pos,r}=this;
            ctx.save();
            ctx.setLineDash([6]);
            ctx.beginPath();
            ctx.arc(pos.x,pos.y,r,0,Math.PI*2);
            ctx.stroke();
            ctx.restore();
        }
    }

    //线的基点
    const basicPos=new Vector2(width/2,height/2);
    //线的长度
    const r1=100;
    //大圆半径 r2
    const r2=200;

    //线对象
    const line=new Line(basicPos.clone(),new Vector2(basicPos.x+r1,basicPos.y));
    //小圆对象
    const circle1=new Circle(basicPos.clone(),r1);
    //大圆对象
    const circle2=new Circle(basicPos.clone(),r2);

    //渲染
    render();

    window.addEventListener('mousemove',mousemoveFn);

    function mousemoveFn(event){
        //鼠标位置 mousePos
        const mousePos=new Vector2(event.clientX,event.clientY);
        //鼠标位置减去基点位置,得到鼠标相对于基点的位置 mouseSubPos
        const mouseSubBasic=mousePos.sub(basicPos);
        //获取鼠标到基点的距离,计算此距离与大圆半径的比例 ratio
        let ratio=mouseSubBasic.length()/r2;

        //让比例不超过1
        ratio=Math.min(ratio,1);

        //计算向量长度 len
        const len=r1*ratio;

        //让mouseSubBasic向量的长度和r1相等
        const pos=mouseSubBasic.setLength(len);
        //基于基点位置偏移向量
        pos.add(basicPos);
        //更新线的终点位置
        line.end.copy(pos);
        //渲染
        render();
    }

    //渲染方法
    function render(){
        ctx.clearRect(0,0,width,height);
        line.draw(ctx);
        circle1.draw(ctx);
        circle2.draw(ctx);

    }

</script>

指向鼠标的线三维.gif

7、404页面视线跟随

<script type="module">
    import Vector2 from "./jsm/Vector2.js";

    const [width,height]=[window.innerWidth,window.innerHeight];
    const canvas=document.getElementById('canvas');
    canvas.width=width;
    canvas.height=height;
    const  ctx=canvas.getContext('2d');

    //img 加载器
    const ImgLoader={
        onload(imgs,fn){
            const imgPros=imgs.map((ele)=>{
                return ImgLoader.imgPro(ele);
            });
            Promise.all(imgPros).then((val)=>{
                fn(val);
            },()=>{
                console.error('图片加载失败');
            });
        },
        imgPro(img){
            return new Promise((resolve)=>{
                img.onload=function(){
                    resolve(img);
                }
            });
        }
    };
    //身体
    class BodyRect{
        constructor(img,pos){
            this.img=img;
            this.pos=pos;
        }
        draw(ctx){
            const {img,pos}=this;
            ctx.save();
            ctx.drawImage(img,pos.x,pos.y);
            ctx.restore();
        }
    }
    //眼睛
    class EyeRect{
        constructor(img,pos,offset){
            this.img=img;
            this.pos=pos;
            this.offset=offset;
        }
        draw(ctx){
            const {img,pos,offset}=this;
            ctx.save();
            ctx.drawImage(img,pos.x-offset,pos.y-offset);
            ctx.restore();
        }
    }

    //眼睛的感知距离
    const maxR=50;
    //眼眶的半径
    const rimR=15;
    //两只眼睛相对于怪兽的位置
    const basicPos=[new Vector2(126,52),new Vector2(219,59)];
    //眼瞳的偏移距离。drawImage 时,眼球的基点在左上角,需调整致中心
    const eyeR=11;
    //身体和眼睛
    let monsterBody,eyeLeft,eyeRight;
    //移动变换的位置
    let monsterPos=getMonsterPos();

    //怪物身体图片
    const bodyImg=new Image();
    bodyImg.src='./images/404.png';
    //眼睛图片
    const eyeImg=new Image();
    eyeImg.src='./images/eye.png';
    //当两个图片加载成功后,渲染图形
    ImgLoader.onload([bodyImg,eyeImg],loadedFn);


    function loadedFn(){
        //建立身体和眼睛
        monsterBody=new BodyRect(bodyImg,new Vector2(0,0));
        eyeLeft=new EyeRect(eyeImg,basicPos[0].clone(),eyeR);
        eyeRight=new EyeRect(eyeImg,basicPos[1].clone(),eyeR);

        //渲染
        render();

        //添加鼠标移动事件
        canvas.addEventListener('mousemove',mousemoveFn);
        //监听窗口尺寸的改变
        window.addEventListener('resize',resizeFn);
    }

    //渲染方法
    function render(){
        //清理
        ctx.clearRect(0,0,width,height);
        //渲染
        ctx.save();
        ctx.translate(monsterPos.x,monsterPos.y);
        monsterBody.draw(ctx);
        eyeLeft.draw(ctx);
        eyeRight.draw(ctx);
        ctx.restore();
    }

    //鼠标移动
    function mousemoveFn(event){
        //获取鼠标位置
        const mousePos=new Vector2(event.clientX,event.clientY).sub(monsterPos);
        //遍历两个眼瞳
        [eyeLeft,eyeRight].forEach((ele,ind)=>{
            //获取鼠标到眼瞳基点的距离
            const mouseSubObj=mousePos.clone().sub(basicPos[ind]);
            //以眼睛中心为基点,按照比例计算瞳孔到圆心的距离
            const radius=Math.min(mouseSubObj.length()/maxR,1)*rimR;
            //设置眼瞳位置
            const pos=mouseSubObj.setLength(radius).add(basicPos[ind]);
            //为眼睛的位置赋值
            ele.pos.copy(pos);
        });
        //渲染
        render();
    }

    //获取怪兽位置
    function getMonsterPos(){
        return new Vector2(canvas.width-500,canvas.height-500);
    }

    //窗口尺寸改变
    function resizeFn(){
        const [width,height]=[window.innerWidth,window.innerHeight];
        canvas.width=width;
        canvas.height=height;
        monsterPos=getMonsterPos();
        render();
    }
</script>

404视线跟随.gif

putImageData 与合成

<script>
    const [width,height]=[window.innerWidth,window.innerHeight];
    const canvas=document.getElementById('canvas');
    canvas.width=width;
    canvas.height=height;
    const  ctx=canvas.getContext('2d');

    const imgDt=ctx.createImageData(400,400);
    imgDt.data.forEach((ele,ind)=>{
        imgDt.data[ind]=255;
    })

    /*全局透明度*/
    /*ctx.globalAlpha=1;
    ctx.putImageData(imgDt,0,0);

    //白色的矩形
    ctx.fillStyle='#fff';
    ctx.fillRect(400,0,400,400);*/

    /*路径裁剪*/
    //路径
    /*ctx.beginPath();
    ctx.arc(400,400,200,0,Math.PI*2);
    ctx.stroke();
    ctx.clip();

    //白色的矩形
    ctx.fillStyle='#fff';
    ctx.fillRect(400,0,400,400);

    ctx.putImageData(imgDt,0,0);*/

    /*全局合成*/
    //已绘图形
    /*ctx.beginPath();
    ctx.arc(400,400,200,0,Math.PI*2);
    ctx.fill();

    //globalCompositeOperation
    ctx.globalCompositeOperation='source-in';

    //将绘图形
    // ctx.fillStyle='#fff';
    // ctx.fillRect(400,0,400,400);

    ctx.putImageData(imgDt,0,0);*/

    /*putImageData 裁剪的正确解锁方式*/
    //已绘图形
    ctx.putImageData(imgDt,0,0);

    //globalCompositeOperation
    ctx.globalCompositeOperation='destination-in';

    //将绘图形
    ctx.beginPath();
    ctx.arc(400,400,200,0,Math.PI*2);
    ctx.fill();

</script>

canvas绘制复杂图形

<embed id="svg"
       src="./images/mount.svg"
       width="300"
       height="300"
       type="image/svg+xml"
       pluginspage="http://www.adobe.com/svg/viewer/install/" />

<canvas id="canvas"></canvas>
<script type="module">
    const canvas=document.getElementById('canvas');
    canvas.width=300;
    canvas.height=300;
    const ctx=canvas.getContext('2d');

    //embed 获取svg容器
    const embed=document.querySelector('embed');

    /*当页面内容加载成功
    *   getSVGDocument() 获取svg 容器中的svg文档对象
    *   querySelector() 通过svg文档对象获取其中的多边形节点
    *   parsePoints() 解析多边形节点中的顶点数据
    *   draw() 所以顶点数据,绘制canvas 图形
    * */
    window.onload=function(){
        const svgDom=embed.getSVGDocument();
        const poly=svgDom.querySelector('#poly');
        console.log('poly',poly);
        const points=parsePoints(poly.getAttribute('points'));
        console.log('points',points);
        /*canvas 绘图*/
        ctx.beginPath();
        points.forEach(p=>{
            ctx.lineTo(p[0],p[1]);
        })
        ctx.fill();
    }


    /*解析svg中的polygon
    *   arr 用空格将points 属性切割为数组,如['x,y',...]
    *   遍历vertices,获取其中每个元素
    *   如果元素不为空
    *   vertice 将字符串类型的点位转化为整数类型的点位,如 '1.2,2.8' → [1,3]
    *
    * */
    function parsePoints(points){
        const vertices=[];
        let arr=points.split(' ');
        for(let ele of arr){
            if(ele){
                const vertice=ele.split(',').map((num)=>{
                    return Math.round(num);
                });
                vertices.push(vertice);
            }
        }
        return vertices;
    }
</script>