Bootstrap

canvas从零到一,实际案例

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 stops
gradient.addColorStop(0, 'green');
gradient.addColorStop(.5, 'cyan');
gradient.addColorStop(1, 'red');

// Set the fill style and draw a rectangle
ctx.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.生成海报

注意: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 => {
    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文件里面的主流程