最近在研究canves,想實(shí)現(xiàn)一個(gè)可以在畫布中操作上傳的內(nèi)容讳侨,不經(jīng)意間發(fā)現(xiàn)了個(gè)插件Fabric.js呵萨。Fabric.js?是一個(gè)強(qiáng)大的H5 canvas框架奏属,在原生canvas之上提供了交互式對(duì)象模型跨跨,通過簡(jiǎn)潔的api就可以在畫布上進(jìn)行豐富的操作。該框架是個(gè)開源項(xiàng)目囱皿,官網(wǎng)地址: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頁面中寫一個(gè)350 x 200的canvas標(biāo)簽, 這里不寫寬高也行勇婴,后面可以通過js來設(shè)置寬高
<canvas id="canvas" width="350" height="200"></canvas>
初始化fabric的canvas對(duì)象,創(chuàng)建一個(gè)卡片(后面都用card表示畫布對(duì)象)
constcard =newfabric.Canvas('canvas')// ...這里可以寫canvas對(duì)象的一些配置嘱腥,后面將會(huì)介紹
// 如果<canvas>標(biāo)簽沒設(shè)置寬高耕渴,可以通過js動(dòng)態(tài)設(shè)置
card.setWidth(350)card.setHeight(200)
設(shè)置畫布和監(jiān)聽事件
// 在canvas對(duì)象初始化后,通過以下方式監(jiān)聽
? ? ? ? // 比如監(jiān)聽畫布的圖層編輯事件
? ? ? ? exportCanves.onclick = function(e) {
? ? ? ? ? ? // 導(dǎo)出當(dāng)前畫布信息
? ? ? ? ? ? const currState = card.toJSON(); // 導(dǎo)出的Json如下圖
? ? ? ? ? ? console.log(currState)
? ? ? ? ? ? // 加載畫布信息
? ? ? ? ? ? // card.loadFromJSON(lastState, () => {
? ? ? ? ? ? // ? ? card.renderAll();
? ? ? ? ? ? // });
? ? ? ? ? ? const dataURL = card.toDataURL({
? ? ? ? ? ? ? ? format: 'jpeg', // jpeg或png
? ? ? ? ? ? ? ? quality: 1.8 // 圖片質(zhì)量齿兔,僅jpeg時(shí)可用
? ? ? ? ? ? ? ? // 截取指定位置和大小
? ? ? ? ? ? ? ? //left: 100,
? ? ? ? ? ? ? ? //top: 100,
? ? ? ? ? ? ? ? //width: 200,
? ? ? ? ? ? ? ? //height: 200
? ? ? ? ? ? });
? ? ? ? ? ? downloadIamge(dataURL, 'img')
? ? ? ? }
? ? ? ? card.on('object:modified', (e) => {
? ? ? ? ? ? console.log(e.target) // e.target為當(dāng)前編輯的Object
? ? ? ? ? ? objectMoving(e)
? ? ? ? ? ? objectModified(e)
? ? ? ? ? ? // ...旋轉(zhuǎn)橱脸,縮放,移動(dòng)等編輯圖層的操作都監(jiān)聽到
? ? ? ? ? ? // 所以如果有撤銷/恢復(fù)的場(chǎng)景分苇,這里可以保存編輯狀態(tài)
? ? ? ? });
? ? ? ? // 設(shè)置畫布背景顏色
? ? ? ? card.backgroundColor = 'white';
? ? ? ? // 或者
? ? ? ? // card.setBackgroundColor('blue');
? ? ? ? let selectedObj
? ? ? ? // 方式二
? ? ? ? card.on('selection:created', (e) => {
? ? ? ? ? ? // 選中圖層事件觸發(fā)時(shí)添诉,動(dòng)態(tài)更新賦值
? ? ? ? ? ? console.log(e.target)
? ? ? ? ? ? selectedObj = e.target
? ? ? ? ? ? // 順時(shí)針90°旋轉(zhuǎn)
? ? ? ? ? ? const currAngle = selectedObj.angle; // 當(dāng)前圖層的角度
? ? ? ? ? ? const angle = currAngle === 360 ? 90 : currAngle + 90;
? ? ? ? ? ? // selectedObj.rotate(angle);
? ? ? ? ? ? // 如果是通過滑塊的方式控制旋轉(zhuǎn)
? ? ? ? ? ? // selectedObj.rotate(slideValue);
? ? ? ? ? ? // 水平翻轉(zhuǎn),同理垂直翻轉(zhuǎn)改為scaleY屬性
? ? ? ? ? ? // selectedObj.set({
? ? ? ? ? ? // ? ? scaleX: -selectedObj.scaleX,
? ? ? ? ? ? // })
? ? ? ? ? ? //移除圖層
? ? ? ? ? ? // card.remove(selectedObj) // 傳入需要移除的object
? ? ? ? ? ? // 所有圖層的操作之后医寿,都需要調(diào)用這個(gè)方法
? ? ? ? ? ? card.renderAll()
? ? ? ? })
操作畫布中的圖層和文字
當(dāng)對(duì)象移動(dòng)時(shí) 限制對(duì)象的 不超出畫布栏赴,當(dāng)畫布對(duì)象放大時(shí)限制其操出邊界
<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">導(dǎo)出圖片</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編碼數(shù)據(jù)
? ? ? ? ? ? ? ? let a = document.createElement("a"); // 生成一個(gè)a元素
? ? ? ? ? ? ? ? let event = new MouseEvent("click"); // 創(chuàng)建一個(gè)單擊事件
? ? ? ? ? ? ? ? a.download = name || "photo"; // 設(shè)置圖片名稱
? ? ? ? ? ? ? ? a.href = url; // 將生成的URL設(shè)置為a.href屬性
? ? ? ? ? ? ? ? a.dispatchEvent(event); // 觸發(fā)a的單擊事件
? ? ? ? ? ? };
? ? ? ? ? ? image.src = imgsrc;
? ? ? ? }
? ? ? ? //點(diǎn)擊添加背景圖片
? ? ? ? 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]);
? ? ? ? ? ? // 讀取圖片地址,設(shè)置畫布背景圖片
? ? ? ? ? ? fabric.Image.fromURL(path, (img) => {
? ? ? ? ? ? ? ? img.set({
? ? ? ? ? ? ? ? ? ? // 通過scale來設(shè)置圖片大小靖秩,這里設(shè)置和畫布一樣大
? ? ? ? ? ? ? ? ? ? scaleX: card.width / img.width,
? ? ? ? ? ? ? ? ? ? scaleY: card.height / img.height,
? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? // 設(shè)置背景
? ? ? ? ? ? ? ? card.setBackgroundImage(img, card.renderAll.bind(card));
? ? ? ? ? ? ? ? card.renderAll();
? ? ? ? ? ? });
? ? ? ? }
? ? ? ? // 點(diǎn)擊添加文字
? ? ? ? addtext.onclick = function(e) {
? ? ? ? ? ? getDialog()
? ? ? ? }
? ? ? ? // 點(diǎn)擊刪除元素
? ? ? ? remove.onclick = function(e) {
? ? ? ? ? ? card.remove(card.getActiveObject())
? ? ? ? ? ? card.renderAll()
? ? ? ? }
? ? ? ? // 當(dāng)對(duì)象移動(dòng)時(shí) 限制對(duì)象的 不超出畫布
? ? ? ? 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);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 當(dāng)畫布對(duì)象放大時(shí)限制其操出邊界:
? ? ? ? //在對(duì)象修改時(shí)方法里就可以調(diào)用了须眷。
? ? ? ? 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: "請(qǐng)輸入你要添加的內(nèi)容",
? ? ? ? ? ? ? ? html: '',
? ? ? ? ? ? }).onClose((cc, isOk, done) => {
? ? ? ? ? ? ? ? let tmr = null;
? ? ? ? ? ? ? ? if (cc) { //true 確認(rèn)
? ? ? ? ? ? ? ? ? ? isOk.showLoading();
? ? ? ? ? ? ? ? ? ? if (addtextByCanves(isOk.inputValue)) {
? ? ? ? ? ? ? ? ? ? ? ? isOk.hideLoading();
? ? ? ? ? ? ? ? ? ? ? ? done();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? clearTimeout(tmr);
? ? ? ? ? ? ? ? ? ? isOk.hideLoading();
? ? ? ? ? ? ? ? ? ? done();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? }
? ? ? ? //將輸入的內(nèi)容加入到canves中
? ? ? ? function addtextByCanves(text) {
? ? ? ? ? ? const contant = new fabric.Textbox(text, {
? ? ? ? ? ? ? ? left: 50,
? ? ? ? ? ? ? ? top: 50,
? ? ? ? ? ? ? ? width: 150,
? ? ? ? ? ? ? ? fontSize: 20, // 字體大小
? ? ? ? ? ? ? ? fontWeight: 800, // 字體粗細(xì)
? ? ? ? ? ? ? ? // fill: 'red', // 字體顏色
? ? ? ? ? ? ? ? // fontStyle: 'italic', ?// 斜體
? ? ? ? ? ? ? ? // fontFamily: 'Delicious', // 設(shè)置字體
? ? ? ? ? ? ? ? // stroke: 'green', // 描邊顏色
? ? ? ? ? ? ? ? // strokeWidth: 3, // 描邊寬度
? ? ? ? ? ? ? ? hasControls: false,
? ? ? ? ? ? ? ? borderColor: 'orange',
? ? ? ? ? ? ? ? editingBorderColor: 'blue' // 點(diǎn)擊文字進(jìn)入編輯狀態(tài)時(shí)的邊框顏色
? ? ? ? ? ? });
? ? ? ? ? ? // 添加文字
? ? ? ? ? ? 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);
? ? ? ? ? ? ? ? // 添加對(duì)象
? ? ? ? ? ? ? ? card.add(img);
? ? ? ? ? ? });
? ? ? ? }