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 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.生成海报
解决屏幕分辨率不同产生的模糊问题
直接 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';
@Component
export 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 加入
还未添加个人简介
评论