效果展示
功能
github 代码地址
项目地址中针对画板中的功能都单独做了抽离。如果想了解某个功能的话可以直接打开对应的文件查看效果。
如何给 canvas 设置背景
首先我们会想到
- 直接给 canvas 元素设置 css 背景颜色。
- 使用 fill / fillRect 给画板填充颜色。
canvas 设置 css 背景
这种方式有一些局限性
- 当我们使用在画板上使用 fillRect 之后,是无法再在通过 css 改变颜色。
- 相当于 canvas 本身是透明的可以通过 css 来设置底色。当 canvas 上加了一层有色图层,那么就无法看到原先的底色了。
- 我们 css 无法局部使用印花的效果。类似下图效果
canvas 使用 fillRect
使用 css 有这些局限,我们就使用 canvas 来绘制一个真正的背景层。
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 150, 100);
上面代码确实可以在 canvas 填充成对应的颜色。
问题1
但是如果画板上要是本身就已经绘制了内容,如果你使用 fillRect 会将之前绘制的内容覆盖。
这是由于 CanvasRenderingContext2D.globalCompositeOperation
默认是 source-over
- source-over 解释为: setting and draws new shapes on top of the existing canvas content
- destination-over 解释为:New shapes are drawn behind the existing canvas content.
我们绘制的背景应该在已有图层的后面。所以我们可以将设置为 ctx.globalCompositeOperation = 'destination-over'
两种背景的更新如下图。source-over 是覆盖在已经绘制的内容之上。 destination-over 是绘制在之后。
问题2
使用上面的办法确实是可以在现有内容后绘制一层背景层。但是当我们想要再次改变背景颜色时候怎么办?
我们再次更新背景时候还是绘制在已有内容之后,所以是看不到我们绘制的内容的。
解决方案
首先我们了解一个 api
- 这里的 image 包括 HTMLImageElement 、HTMLCanvasElement 、SVGImageElement 、HTMLVideoElement 等元素。
这样我们使用两个 canvas 来完成更换背景的操作。他们各自分工,一个作为内容层、一个作为背景层。背景层使用 CanvasRenderingContext2D.drawImage() 将内容层渲染到背景层上。
画板导入图片进行编辑
如何将本地图片导入绘制到画板上
首先来介绍 FileRender api,通过这个 FileReader 来读取本地的图片文件。
- 使用 FileReader 读取 blob 文件转为 base64的地址,
- 在 load 事件中拿到地址赋值给 img 元素,得到一个 img 对象。(这里可以定义图片的宽高等操作)
- 得到图片后我们可以使用 drawImage 将图片绘制到 canvas 上。
/**
* 读取 input 元素选择的文件转化成 Base64 的地址,并根据该地址创建 img 元素
* @param blob File 对象
* @param width 指定图片的宽度
*/
export function blobToImg (blob:Blob) {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.addEventListener('load', () => {
let img = new Image()
img.src = reader.result as string
img.addEventListener('load', () => {
resolve(img)
}
)
})
reader.readAsDataURL(blob)
})
}
对图片进行拖拽、拉伸
操作框
操作框是用于判断当前要进行什么操作的。
- 当落在八个不同方向的小矩形中的时候进行各个方向的拉伸。
- 当落在图片上进行移动操作
使用 new Path2D() 创建控制框路径(矩形框和边角的控制框),并且将这些路径存储起来,之后通过 ctx.isPointInPath(x,y)
来判断当前落点是否在对于的框内。
移动图片
首先我们知道一开始图片是通过 ctx.drawImage(img,x,y,w,h)
来将本地图片绘制到 canvas 上的。
当我们移动图片的时候我们需要考虑的只有 x,y
坐标位置。 w,h
是不需要改变的,所以我们移动时候 mousemove
事件中监听坐标位置变换值,然后将图片重新绘制到新的坐标即可。
拉伸图片
八个方向的拉伸考虑的会有一些不一样。例如
- 右上角拉伸的时候,我们需要考虑的只有
w,h
的变换。因为起点位置是不变的。 - 左上角的拉伸的时候,
x,y,w,h
都会有变换。 - ......
总的来说拉伸图片也是去改变 x,y,w,h
的值,然后根据这些值重新去渲染图片。
文字输入效果
模拟光标
模拟光标就是在鼠标点击点位置绘制一个类似光标的矩形,然后让这个矩形消失 -> 出现 -> 消失的过程。来看两个api。
- 通过 getImageData 来将当前画板上的像素都存储起来。(相当于我们保存当前 canvas 的快照)。
- 绘制光标矩形
- 使用 putImageData 将之前保存的快照应用到 canvas 上。(相当于返回到第一步的画面)
- 重复 2,3步骤。实现光标闪烁效果。
文字自动换行
CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);
fillText 可以将文字绘制指定的位置,但是不会自动换行。如果想要实现自动换行需要了解另外一个知识点。
SVG forginObject元素与文本自动换行
在 svg 中使用 foreignObject 标签可以直接在SVG内部嵌入XHTML元素。当超过设定当宽度后,XHML 元素本身是自带换行的。
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="120" height="50">
<body xmlns="http://www.w3.org/1999/xhtml">
<p style="font-size:12px;margin:0;">一段需要word wrap的文字。</p>
</body>
</foreignObject>
</svg>
SVG forginObject元素生成图片
- 将html标签拼接到body之中:
<svg xmlns="http://www.w3.org/2000/svg"><body><p style="width:100px"></p></body><foreignObject><body></body></foreignObject></svg>
格式中。 - 创建 img 元素将步骤1的地址赋值给 img 元素。
- 使用 drawImage 将图片位置到 canvas 上
- 如果你要保存图片可以使用
canvas.toDataURL()
或canvas.toBlob()
将图片重新导出。
canvas输入文字自动换行。
export async function autoWrapText(width:number,height:number,value:string,font="16px sans-serif"):Promise<HTMLImageElement>{
const { fontColor } = store.state
// 这里用的color不能使用 hex 如:#000,无效
const path = 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="'+ width +'" height="'+ height +'"><body xmlns="http://www.w3.org/1999/xhtml" style="margin:0;font:'+ font +';word-break: break-word;color:'+fontColor+'">'+value+'</body></foreignObject></svg>';
return await loadImgSize(path,width,height)
}
function loadImgSize(path:string,width:number,height:number):Promise<HTMLImageElement> {
return new Promise(res=>{
const img = new Image()
img.width = width
img.height = height
img.onload = function () {
res(img)
}
img.src = path
})
}
- 监听键盘事件,判断当前输入内容或者功能键(如果要做特殊功能的话)。
- 在 mousedown 中记录当前鼠标的位置,计算出距离 canvas 画板右侧的距离记为 remain 变量。将 svg foreignObject 标签的宽度设置为 remain。
- 创建 img 标签。将地址拼接成
data:image/svg+xml...
格式 - 使用 drawImage 将img绘制到 canvas上。
前端图片上传前压缩
前端压缩的好处
- 由于上传图片尺寸比较小,因此上传速度会比较快,交互会更加流畅,同时大大降低了网络异常导致上传失败风险。
- 最最重要的体验改进点:省略了图片的再加工成本。
流程
图片(本地选取或路径) -> canvas 压缩(使用 ctx.drawImage(img,0,0,w,h)来实现) -> 图片(使用 canvas.toDataURL() 、 canvas.toBlob())方法重新导出图片。
使用 ctx.drawImage 来指定 w,h
按比例重新渲染到 canvas,然后导出图片实现压缩。
更多功能查看 github 代码
参考
SVG 简介与截图等应用
mdn
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!