canvas 从零到一,实际案例
发布于: 2021 年 05 月 14 日
1.绘制路径
1.开始绘制:ctx.beginPath(); 重点,隔离产生一个域
2.描边宽度:ctx.lineWidth
3.描边颜色:ctx.strokeStyle = 'red';
4.路径开始点:ctx.moveTo(x, y);
5.路劲目标点:ctx.lineTo(x, y);
6.绘制路径:ctx.stroke();
7.关闭路径:ctx.closePath();
复制代码
 2.绘制矩形
1.矩形方法:ctx.rect(x, y, w, h);
2.设置填充颜色:ctx.fillStyle = '#987';
3.执行填充动作:ctx.fill();
4.绘制一个填充的矩形:ctx.fillRect(x, y, w, h); 没有描边
5.绘制一个描边矩形:ctx.strokeRect(x, y, width, height);
6.清除画布:ctx.clearRect(x, y, width, height);复制代码
 3.绘制圆
    ctx.beginPath();    ctx.arc(x, y, r, startAngle, endAngle(Math.PI),);
    ctx.moveTo(100, 50);    ctx.lineTo(100, 100);    ctx.lineTo(150, 100);        ctx.fill();复制代码
 4.绘制文字
1.设置字体大小与字体库: ctx.font = `${fontSize}px serif`;
2.设置基准线:ctx.textBaseline = 'middle';
3.绘制字体:ctx.fillText('sdddddddddd但是v', 0, 0);
4.获取文本信息: ctx.measureText(text);
5.demo:    ctx.save();    ctx.translate(0, (fontSize / 2)); // 适配安卓 ios 下的文字居中问题    ctx.fillText(text, 0, 0);    ctx.restore();复制代码
 5.绘制图片
// 缩放图片,在canvas上展示const maxW = 100;const multiple = img.width /  maxW;ctx.drawImage(img, x, y, img.width, img.height, 0, 0, maxW, img.height / multiple);复制代码
 6.制作渐变线条,矩形
const gradient = ctx.createLinearGradient(20,0, 220,0);
// Add three color stopsgradient.addColorStop(0, 'green');gradient.addColorStop(.5, 'cyan');gradient.addColorStop(1, 'red');
// Set the fill style and draw a rectanglectx.fillStyle = gradient;复制代码
 7.重点方法
1.移动画板的位置:ctx.translate(x, y);
2.旋转画板:ctx.rotate(Math.PI / 180 * Angle);
3.放大缩小画板:ctx.scale(x, y);
4.保存状态:ctx.save();
5.恢复状态:ctx.restore();
6.ctx.transform(a, b, c, d, e, f);	a:水平缩放  b:垂直倾斜  c:水平偏斜  d:垂直缩放  e:水平平移  f:垂直平移  7.裁剪路径的区域:ctx.clip(path,"nonzero":非零缠绕规则,默认规则。"evenodd");复制代码
 8.封装生成圆形图片的方法
/** * @description: 生成圆角矩形 * @param {*} ctx * @param {number} x * @param {number} y * @param {number} w * @param {number} h * @param {number} r * @param {string} color * @param {string} type:fill | stroke | undefined * @return void */const roundedRectangle = (ctx, x: number, y: number, w: number, h: number, r: number, color = '#000', type?: string | undefined) => {    const PI = Math.PI / 180;    ctx.translate(x, y);    ctx.save();    ctx.beginPath();    // ctx.moveTo(0, 0);    // ctx.lineTo(w, 0);
    ctx.arc(w - r, r, r, PI * 270, PI * 360);    // ctx.lineTo(w, h);
    ctx.arc(w - r, h - r, r, PI * 0, PI * 90);    // ctx.lineTo(0, h);
    ctx.arc(r, h - r, r, PI * 90, PI * 180);    // ctx.lineTo(0, 0);
    ctx.arc(r, r, r, PI * 180, PI * 270);    ctx.closePath();    ctx.fillStyle = color;    switch (type) {        case 'fill':            ctx.fill();            break;        case 'stroke':            ctx.stroke();            break;    }    ctx.restore();};
/** * 生成圆角图片 * @param ctx:canvas上下文 * @param img:图片实列 * @param x:坐标 * @param y:坐标 * @param w:宽 * @param h:高 * @param r:圆角 */interface Img {    width: number;    height: number;}const roundedRectangleImg = (ctx, img: Img, x: number, y: number, w: number, h: number, r = 0) => {    ctx.save();    // 缩小倍数    const multiple = img.width / w;    roundedRectangle(ctx, x, y, w, h, r);    ctx.clip();    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, img.height / multiple);    ctx.restore();};/** * @description: 绘制文字 * @param {*} ctx * @param {string} text * @param {*} fontSize * @return {*} */const writeText = (ctx, text: string, fontSize = 12, x = 0, y = 0, color = '#000') => {    ctx.save();    ctx.font = `${fontSize}px serif`;    ctx.fillStyle = color;    ctx.textBaseline = 'middle';    ctx.translate(0, (fontSize / 2)); // 适配安卓 ios 下的文字居中问题    ctx.fillText(text, x, y);    ctx.measureText(text);    ctx.restore();};复制代码
 9.生成海报
- 解决屏幕分辨率不同产生的模糊问题 
- 直接 canvas 渲染海报在 dom 节点上,不用再像以前的方式写两套(canvas,css) 
- 没使用 htmlCanvas 
- 可支持放大几倍屏幕的导出图片 
注意:ios 最大支持 200W 像素(w*h),canvas 支持 500w
/** * @description: 生成圆角矩形 * @param {*} ctx * @param {number} x * @param {number} y * @param {number} w * @param {number} h * @param {number} r * @param {string} color * @param {string} type:fill | stroke | undefined * @return void */const roundedRectangle = (ctx, x: number, y: number, w: number, h: number, r: number, color = '#000', type?: string | undefined) => {    const PI = Math.PI / 180;    ctx.translate(x, y);    ctx.save();    ctx.beginPath();    // ctx.moveTo(0, 0);    // ctx.lineTo(w, 0);
    ctx.arc(w - r, r, r, PI * 270, PI * 360);    // ctx.lineTo(w, h);
    ctx.arc(w - r, h - r, r, PI * 0, PI * 90);    // ctx.lineTo(0, h);
    ctx.arc(r, h - r, r, PI * 90, PI * 180);    // ctx.lineTo(0, 0);
    ctx.arc(r, r, r, PI * 180, PI * 270);    ctx.closePath();    ctx.fillStyle = color;    switch (type) {        case 'fill':            ctx.fill();            break;        case 'stroke':            ctx.stroke();            break;    }    ctx.restore();};
/** * 生成圆角图片 * @param ctx:canvas上下文 * @param img:图片实列 * @param x:坐标 * @param y:坐标 * @param w:宽 * @param h:高 * @param r:圆角 */interface Img {    width: number;    height: number;}const roundedRectangleImg = ({ctx, img, x, y, w, h, r = 0}) => {    ctx.save();    // 缩小倍数    const multiple = img.width / w;    roundedRectangle(ctx, x, y, w, h, r);    ctx.clip();    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, img.height / multiple);    ctx.restore();};/** * @description: 绘制文字 * @param {*} ctx * @param {*}canvas * @param {string} text * @param {*} fontSize * @param {*} textSafeArea: 文字安全区域 * @return {*} */const writeText = (ctx, canvas, text: string, x = 0, y = 0, fontSize = 12, color = '#000', textSafeArea?) => {    ctx.save();    ctx.font = `${fontSize}px serif`;    ctx.fillStyle = color;    ctx.textBaseline = 'middle';    ctx.textAlign = 'center';
    ctx.translate(0, (fontSize / 2)); // 适配安卓 ios 下的文字居中问题    const textMsg: TextMetrics = ctx.measureText(text);    if (canvas.width < textMsg.width || !!textSafeArea && textSafeArea < textMsg.width) {        const newText = text.slice(0, text.length > 10 ? 10 : text.length);        ctx.fillText(newText + '...', x, y);        console.log(textMsg, 'textMsg', textMsg.width, canvas.width, text.slice(0, 10));
    } else {        ctx.fillText(text, x, y);    }    ctx.restore();};
interface CanvasCtx {    canvas: HTMLCanvasElement;    ctx: CanvasRenderingContext2D | null;    postersEle: HTMLDivElement | null;    cPx: number;    cW: number;    cH: number;}/** * @description: 初始化一画布 * @param {*} canvas:canvas实例 * @param {*} id:标签id * @param {*} w:canvas宽度 * @param {*} h:canvas高度 * @param {*} d:放大canvas倍数 * @return {} cPx:基于750计算出来的相对单位 */const initCanvas = ({ id, w = 0, h = 0, d = 1, canvas }): CanvasCtx => {    // 获取设备像素比    const dPR = Math.round(window.devicePixelRatio);    const postersEle: HTMLDivElement | null = document.querySelector(id);    const screenW = window.screen.width;    const screenH = window.screen.height;    // 先获取节点下是否存在pl-canvas节点    const canvasName = '#pl-canvas';    const canvasDom: HTMLCanvasElement | null = document.querySelector(canvasName);
    if (!canvasDom) {        canvas = document.createElement('canvas');        canvas.setAttribute('id', 'pl-canvas');        canvas.style.width = '100%';        postersEle?.append(canvas);            }    const ctx: CanvasRenderingContext2D | null = canvas.getContext('2d');    const cW = (w || screenW) * d * dPR;    const cH = (h || screenH) * d * dPR;    canvas.width = cW;    canvas.height = cH;    const cPx = ((750 / 100) / (cW / 100) / d) * d;    return {        canvas,        ctx,        postersEle,        cPx,        cW,        cH,    };};/** * @description: 初始化一张图片实例 * @param {*} async * @return {*} */const loadingPictures = async (url: string): Promise<HTMLImageElement> => {    return new Promise((resolve, reject) => {        const img: HTMLImageElement = new Image();        img.src = url;        img.onload = () => {            resolve(img);        };        img.onerror = (err) => {            console.error(err, url);            reject(null);        };    });};/** * @description: 下载图片 * @param {string} url:下载地址 * @param {string} name:图片名称 * @return {*} */const downloadImage = (url: string, name?: string) => {    const a: HTMLAnchorElement = document.createElement('a');    const event: MouseEvent = new MouseEvent('click');    a.download = name || 'photo';    a.href = url;    a.dispatchEvent(event);};/** * @description: 文件生成临时地址 * @param {*} ArrayBuffer * @param {*} type * @return {*} */interface TemFileAddress {    url: string;    revokeObjectURL: () => void;}const temFileAddress = (file: Blob, type?: string): TemFileAddress => {    const url = window.URL.createObjectURL(file);    const revokeObjectURL = () => {        window.URL.revokeObjectURL(url);    };    return {        url,        revokeObjectURL,    };};复制代码
 1.这是vue文件里面的主流程<template>      <div id='posters'>        <el-Button @click="generateImages"> img </el-Button>        <img :src="img" alt="">    </div></template>
<script lang='ts'>import { Component, Vue } from 'vue-property-decorator';import { roundedRectangleImg, convertBase64UrlToBlob, temFileAddress, writeText, initCanvas, loadingPictures, downloadImage } from '../units/canvas';
@Componentexport default class Posters extends Vue {    url: string = require('../assets/1.jpg');    bg: string = require('../assets/p.png');    img: string | undefined = '';    canvas: HTMLCanvasElement | null = null;    async mounted() {        this.init(1);    }    async init(d): Promise<HTMLCanvasElement> {        const { postersEle, ctx, cW, cPx, canvas } = initCanvas({id: '#posters', d, canvas: this.canvas });        this.canvas = canvas;        !!postersEle && (postersEle.style.height = `${window.screen.width * d}px`);        const bg = await loadingPictures(this.bg);        const multiple = (bg.width / cW) * d;        ctx?.drawImage(bg, 0, 0, cW, (bg.height / multiple) * d);        const img: HTMLImageElement = await loadingPictures(this.url);        roundedRectangleImg({ctx, img, x: 330 / cPx, y: 18 / cPx, w: 100 / cPx, h: 100 / cPx, r: 50 / cPx});        writeText(ctx, canvas, '我是文r上etttethdbtrdftrfdrthgdergegf', 380 / cPx, 128 / cPx, 18, '', 750 / cPx);
        roundedRectangleImg(            {                ctx,                img,                x: 75 / cPx,                y: 290 / cPx,                w: 600 / cPx,                h: 300 / cPx,                r: 20 / cPx,            },        );        ctx?.fillRect(0, 0, 100, 100);        return canvas;    }
    async generateImages() {        const canvas = await this.init(1);        this.img = canvas?.toDataURL('image/png', 1);        downloadImage(this.img);    }}</script>复制代码
 划线
评论
复制
发布于: 2021 年 05 月 14 日阅读数: 20
版权声明: 本文为 InfoQ 作者【★】的原创文章。
原文链接:【http://xie.infoq.cn/article/169a4b62c759361d5ea94039f】。文章转载请联系作者。

★
关注
还未添加个人签名 2020.08.05 加入
还未添加个人简介











 
    
评论