核心函数
创建画布
因为我们身体模版的宽高为500x660,所以我们画布也设置成这个大小
<canvas id="canvas"></canvas>
import { fabric } from "fabric";
const instance = new fabric.Canvas('canvas', {
width: 500,
height: 660
});
添加身体
// 添加背景
function addBackground(url) {
return new Promise((resolve) => {
fabric.Image.fromURL(url, img => {
img.scaleToWidth(500);
instance.setBackgroundImage(img, instance.renderAll.bind(instance));
resolve();
}, {
crossOrigin: 'Anonymous'
});
})
}
添加头像
// 添加虚拟头像
function addVirtualAvatar(url) {
return new Promise((resolve) => {
fabric.Image.fromURL(url, img => {
img.scaleToWidth(110);
img.set({
top: 180,
left: 200
});
instance.add(img);
resolve();
}, {
crossOrigin: 'Anonymous'
});
})
}
好像已经可以了?!我们看看效果!
// 添加背景
await addBackground(bgURL);
// 添加头像
await addVirtualAvatar(avatarURL);
// 刷新画布
instance.renderAll();
看着有点奇怪,我们的头部超出了黑色区域!这不是我们想要的效果。
查遍所有参考资料之后,终于找到一个解决方案:globalCompositeOperation
globalCompositeOperation(混合模式)
- 绘制对象为粉色方块
- 现有画布为蓝色方块
列举几种常见模式:
1. source-over
默认模式,绘制对象在现有画布之上,且全部显示。
2. destination-over
绘制对象在现有画布之下,且全部显示。
3. source-atop
绘制对象在现有画布之上,但是只显示与现有画布重叠部分。
4. destination-atop
绘制对象在现有画布之下,但是只显示与现有画布重叠部分。
现在我们就有思路了,如果我们先绘制一个黑色区域部分,然后再绘制头部,并且将头部设置为source-atop,这样我们的头就不会超出去了。
代码改造
头部改造
function addVirtualAvatar(url) {
return new Promise((resolve) => {
fabric.Image.fromURL(url, img => {
img.scaleToWidth(110);
img.set({
top: 180,
left: 200,
globalCompositeOperation: 'source-atop'
});
instance.add(img);
resolve();
}, {
crossOrigin: 'Anonymous'
});
})
}
绘制一个SVG遮盖层
function addSVG(url) {
return new Promise((resolve) => {
fabric.loadSVGFromURL(url, (objects, options) => {
const mask = fabric.util.groupSVGElements(objects, options);
const scale = 500 / 3100;
mask.scale(scale).set({
left: mask.left * scale,
top: mask.top * scale,
})
instance.add(mask);
resolve(mask);
});
})
}
我们绘制遮盖层和头像试试。
// 添加遮盖
await addSVG(SVGURL);
// 添加头像
await addVirtualAvatar(avatarURL);
// 刷新画布
instance.renderAll();
好像达到效果了,那我们再添加背景就可以搞定了 >.<。
// 添加遮盖
await addSVG(SVGURL);
// 添加头像
await addVirtualAvatar(avatarURL);
// 添加背景
await addBackground(bgURL);
// 刷新画布
instance.renderAll();
奇怪了,加上背景之后又变成这样了。但是头部超出身体的部分被隐藏了。
思考后应该是黑色区域和背景图先绘制了被当成了一个整体,然后再绘制的头,这样头部就不会超出身体。
各种实验之后,终于找到了解决办法,不设置backgroundImage,改成设置overlayImage,然后将overlayImage的globalCompositeOperation属性设置为destination-over。
使用overlayImage代替backgroundImage
function addOverlay(url) {
return new Promise((resolve) => {
fabric.Image.fromURL(url, img => {
img.scaleToWidth(500);
img.set({
globalCompositeOperation: 'destination-over'
})
instance.setOverlayImage(img, instance.renderAll.bind(instance));
resolve();
}, {
crossOrigin: 'Anonymous'
});
})
}
// 添加遮盖
await addSVG(SVGURL);
// 添加头像
await addVirtualAvatar(avatarURL);
// 添加背景
await addOverlay(bgURL);
// 刷新画布
instance.renderAll();
结果
终于完成了!
在线展示
完整代码
<!--
* @Description:
* @Version: 2.0
* @Author: Yaowen Liu
* @Date: 2021-12-23 09:53:03
* @LastEditors: Yaowen Liu
* @LastEditTime: 2021-12-23 10:07:28
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="http://fabricjs.com/lib/fabric.js"></script>
<script>
const AVATAR_URL = 'https://cdn.shopifycdn.net/s/files/1/0343/0275/4948/files/vFace.png?v=1634089968';
const BODY_URL = 'https://cdn.shopifycdn.net/s/files/1/0510/1423/8379/files/1598957ed24441c515af24f3bc5faac2_e7e99901-8ca6-48d6-b483-7c218e9703fd.png?v=1623061440';
const SVG_URL = 'https://cdn.shopifycdn.net/s/files/1/0510/1423/8379/files/c9ab3de818e54ee83338537021b4e538_85035b24-be5e-4184-aefb-e2de3ef48312.svg?v=1623061439';
// 实列化
const instance = new fabric.Canvas('canvas', {
width: 500,
height: 660
});
// 添加头
function addVirtualAvatar(url) {
return new Promise((resolve) => {
fabric.Image.fromURL(url, img => {
img.scaleToWidth(110);
img.set({
top: 180,
left: 200,
globalCompositeOperation: 'source-atop'
});
instance.add(img);
resolve();
}, {
crossOrigin: 'Anonymous'
});
})
}
// 添加遮盖
function addSVG(url) {
return new Promise((resolve) => {
fabric.loadSVGFromURL(url, (objects, options) => {
const mask = fabric.util.groupSVGElements(objects, options);
const scale = 500 / 3100;
mask.scale(scale).set({
left: mask.left * scale,
top: mask.top * scale,
})
instance.add(mask);
resolve();
});
})
}
// 添加身体
function addOverlay(url) {
return new Promise((resolve) => {
fabric.Image.fromURL(url, img => {
img.scaleToWidth(500);
img.set({
globalCompositeOperation: 'destination-over'
})
instance.setOverlayImage(img, instance.renderAll.bind(instance));
resolve();
}, {
crossOrigin: 'Anonymous'
});
})
}
// 渲染
async function renderer() {
// 添加遮盖
await addSVG(SVG_URL);
// 添加头像
await addVirtualAvatar(AVATAR_URL);
// 添加背景
await addOverlay(BODY_URL);
// 刷新画布
instance.renderAll();
}
renderer();
</script>
</body>
</html>