最近在研究canves,想实现一个可以在画布中操作上传的内容,不经意间发现了个插件Fabric.js。Fabric.js 是一个强大的H5 canvas框架,在原生canvas之上提供了交互式对象模型,通过简洁的api就可以在画布上进行丰富的操作。该框架是个开源项目,官网地址:Fabric.js Javascript Canvas Library
安装
npm安装
npm install fabric --save
cmd安装
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.min.js"></script>
<script src="https://unpkg.com/coco-modal/coco-modal.min.js"></script> 弹框插件
初始化
首先在html页面中写一个350 x 200的canvas标签, 这里不写宽高也行,后面可以通过js来设置宽高
<canvas id="canvas" width="350" height="200"></canvas>
初始化fabric的canvas对象,创建一个卡片(后面都用card表示画布对象)
constcard =newfabric.Canvas('canvas')// ...这里可以写canvas对象的一些配置,后面将会介绍
// 如果<canvas>标签没设置宽高,可以通过js动态设置
card.setWidth(350)card.setHeight(200)
设置画布和监听事件
// 在canvas对象初始化后,通过以下方式监听
// 比如监听画布的图层编辑事件
exportCanves.onclick = function(e) {
// 导出当前画布信息
const currState = card.toJSON(); // 导出的Json如下图
console.log(currState)
// 加载画布信息
// card.loadFromJSON(lastState, () => {
// card.renderAll();
// });
const dataURL = card.toDataURL({
format: 'jpeg', // jpeg或png
quality: 1.8 // 图片质量,仅jpeg时可用
// 截取指定位置和大小
//left: 100,
//top: 100,
//width: 200,
//height: 200
});
downloadIamge(dataURL, 'img')
}
card.on('object:modified', (e) => {
console.log(e.target) // e.target为当前编辑的Object
objectMoving(e)
objectModified(e)
// ...旋转,缩放,移动等编辑图层的操作都监听到
// 所以如果有撤销/恢复的场景,这里可以保存编辑状态
});
// 设置画布背景颜色
card.backgroundColor = 'white';
// 或者
// card.setBackgroundColor('blue');
let selectedObj
// 方式二
card.on('selection:created', (e) => {
// 选中图层事件触发时,动态更新赋值
console.log(e.target)
selectedObj = e.target
// 顺时针90°旋转
const currAngle = selectedObj.angle; // 当前图层的角度
const angle = currAngle === 360 ? 90 : currAngle + 90;
// selectedObj.rotate(angle);
// 如果是通过滑块的方式控制旋转
// selectedObj.rotate(slideValue);
// 水平翻转,同理垂直翻转改为scaleY属性
// selectedObj.set({
// scaleX: -selectedObj.scaleX,
// })
//移除图层
// card.remove(selectedObj) // 传入需要移除的object
// 所有图层的操作之后,都需要调用这个方法
card.renderAll()
})
操作画布中的图层和文字
当对象移动时 限制对象的 不超出画布,当画布对象放大时限制其操出边界
<div class="button-box">
<div class="upload-box">
<button class="button"><label for="addBgImg">添加背景图片</label></button>
<input style="display: none;" class="button" type="file" accept="image/*" name="uploader-input" class="uploader-file" id="addBgImg">
</div>
<div class="upload-box">
<button class="button"><label for="addimg">添加图片</label></button>
<input style="display: none;" class="button" type="file" accept="image/*" name="uploader-input" class="uploader-file" id="addimg">
</div>
<button class="button" id="addtext">添加文字</button>
<button class="button" id="remove">删除元素</button>
<button class="button" id="exportCanves">导出图片</button>
</div>
const exportCanves = document.getElementById("exportCanves")
const addimg = document.getElementById("addimg")
const addBgImg = document.getElementById("addBgImg")
const addtext = document.getElementById("addtext")
const remove = document.getElementById("remove")
function downloadIamge(imgsrc, name) { //下载图片地址和图片名
let image = new Image();
// 解决跨域 Canvas 污染问题
image.setAttribute("crossOrigin", "anonymous");
image.onload = function() {
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
let url = canvas.toDataURL("image/png"); //得到图片的base64编码数据
let a = document.createElement("a"); // 生成一个a元素
let event = new MouseEvent("click"); // 创建一个单击事件
a.download = name || "photo"; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
};
image.src = imgsrc;
}
//点击添加背景图片
addBgImg.onchange = function(event) {
var $file = event.currentTarget;
var formData = new FormData();
var file = $file.files;
formData = new FormData();
formData.append(file[0].name, file[0]);
var path = window.URL.createObjectURL(file[0]);
// 读取图片地址,设置画布背景图片
fabric.Image.fromURL(path, (img) => {
img.set({
// 通过scale来设置图片大小,这里设置和画布一样大
scaleX: card.width / img.width,
scaleY: card.height / img.height,
});
// 设置背景
card.setBackgroundImage(img, card.renderAll.bind(card));
card.renderAll();
});
}
// 点击添加文字
addtext.onclick = function(e) {
getDialog()
}
// 点击删除元素
remove.onclick = function(e) {
card.remove(card.getActiveObject())
card.renderAll()
}
// 当对象移动时 限制对象的 不超出画布
function objectMoving(e) {
var obj = e.target;
if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
return;
}
obj.setCoords();
// top-left corner
if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left);
}
// bot-right corner
if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width) {
obj.top = Math.min(obj.top, obj.canvas.height -
obj.getBoundingRect().height + obj.top -
obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width -
obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left);
}
}
// 当画布对象放大时限制其操出边界:
//在对象修改时方法里就可以调用了。
function objectModified(e) {
let obj = e.target;
let boundingRect = obj.getBoundingRect(true);
if (boundingRect.left < 0 || boundingRect.top < 0 || boundingRect.left + boundingRect.width > obj.canvas.getWidth() || boundingRect.top + boundingRect.height > obj.canvas.getHeight()) {
obj.top = obj._stateProperties.top;
obj.left = obj._stateProperties.left;
obj.angle = obj._stateProperties.angle;
obj.scaleX = obj._stateProperties.scaleX;
obj.scaleY = obj._stateProperties.scaleY;
obj.setCoords();
obj.saveState();
} else {
obj.saveState();
}
}
//输入弹框
function getDialog() {
window.CocoConfig = {
maskClose: true,
header: true,
footer: true,
title: '提示',
text: '',
width: '300px',
top: '30vh',
inputAttrs: true,
escClose: true,
okButton: true,
cancelButton: true,
okText: '确定',
cancelText: '取消',
closeButton: true,
html: '',
zIndexOfModal: 1995,
zIndexOfMask: 2008,
zIndexOfActiveModal: 2020,
autoFocusOkButton: true,
autoFocusInput: true,
fullScreen: false,
borderRadius: '6px',
blur: false,
buttonColor: '#4285ff',
hooks: {
open() {},
opened() {
},
close() {},
closed() {},
},
destroy: true,
animation: true
}
coco({
title: "请输入你要添加的内容",
html: '',
}).onClose((cc, isOk, done) => {
let tmr = null;
if (cc) { //true 确认
isOk.showLoading();
if (addtextByCanves(isOk.inputValue)) {
isOk.hideLoading();
done();
}
} else {
clearTimeout(tmr);
isOk.hideLoading();
done();
}
});
}
//将输入的内容加入到canves中
function addtextByCanves(text) {
const contant = new fabric.Textbox(text, {
left: 50,
top: 50,
width: 150,
fontSize: 20, // 字体大小
fontWeight: 800, // 字体粗细
// fill: 'red', // 字体颜色
// fontStyle: 'italic', // 斜体
// fontFamily: 'Delicious', // 设置字体
// stroke: 'green', // 描边颜色
// strokeWidth: 3, // 描边宽度
hasControls: false,
borderColor: 'orange',
editingBorderColor: 'blue' // 点击文字进入编辑状态时的边框颜色
});
// 添加文字
card.add(contant);
return true
}
//上传图片事件
document.getElementById('addimg').addEventListener('change', function(event) {
var $file = event.currentTarget;
var formData = new FormData();
var file = $file.files;
formData = new FormData();
formData.append(file[0].name, file[0]);
let path = window.URL.createObjectURL(file[0]);
addimgByCanves(path)
});
//将图片加入canves
function addimgByCanves(imgUrl) {
fabric.Image.fromURL(imgUrl, (img) => {
img.set({
hasControls: true, // 是否开启图层的控件
borderColor: 'orange', // 图层控件边框的颜色
});
//生成的图片缩放到指定的尺寸。
// img.scaleToWidth(500);
img.scaleToHeight(100);
// 添加对象
card.add(img);
});
}