前端js實(shí)現(xiàn)canves畫布中拖拽尚困、放大蠢箩、縮小、旋轉(zhuǎn)圖片和文字事甜,設(shè)置背景圖片谬泌,導(dǎo)出

最近在研究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? ? ?


image

安裝

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);

? ? ? ? ? ? });

? ? ? ? }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市花颗,隨后出現(xiàn)的幾起案子捕传,更是在濱河造成了極大的恐慌,老刑警劉巖扩劝,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乐横,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡今野,警方通過查閱死者的電腦和手機(jī)葡公,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來条霜,“玉大人催什,你說我怎么就攤上這事≡姿” “怎么了蒲凶?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拆内。 經(jīng)常有香客問我旋圆,道長(zhǎng),這世上最難降的妖魔是什么麸恍? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任灵巧,我火速辦了婚禮,結(jié)果婚禮上抹沪,老公的妹妹穿的比我還像新娘刻肄。我一直安慰自己,他們只是感情好融欧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布敏弃。 她就那樣靜靜地躺著,像睡著了一般噪馏。 火紅的嫁衣襯著肌膚如雪麦到。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天欠肾,我揣著相機(jī)與錄音瓶颠,去河邊找鬼。 笑死董济,一個(gè)胖子當(dāng)著我的面吹牛步清,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼廓啊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼欢搜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谴轮,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤炒瘟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后第步,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疮装,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年粘都,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了廓推。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翩隧,死狀恐怖樊展,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堆生,我是刑警寧澤专缠,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站淑仆,受9級(jí)特大地震影響涝婉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔗怠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一墩弯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蟀淮,春花似錦最住、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轧粟。三九已至策治,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兰吟,已是汗流浹背通惫。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留混蔼,地道東北人履腋。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親遵湖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悔政,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容