為了不浪費我白白畫的春聯,做了一個新年圖製造機
還沒有新年圖的可以到下面玩玩看~(快收假了不需要了吧!為什麼我的GA沒有數據QQ)
目前遇到的問題&沒有的功能:(有時間再解決...)
-手機版動不了!(Fabric.js無法拖拉圖片動不了)
-第一次進畫面好像圖片無法拖拉動不了(原因不明)重新整理後就可以
-即時Resize暫時無法即時偵測,不確定該怎麼設計各個canvas大小...所以目前都固定,只有手機和電腦板尺寸(還有bug就先抱歉了?)
開發內容:
主要元件
- Canvas 本體,主要操作邏輯 //NewYearCanvas.js檔案
- 剪裁圖片的彈跳視窗 //CustomModal檔案
開發/說明順序
文章上篇這邊請
- React & Bootstrap & Fabric.js 起手式 (文章上篇)
- 設定 Canvas 背景(文章上篇)
a. 讀取src
b. 設定長寬- 上傳圖片(文章上篇)
a. Modal開啟
b. 上傳圖片&讀取- 剪裁圖片 (<---您的位置在這裡)
a. 添加Clip Path
b. 裁切- 添加裁切好圖片到Canvas上
- 添加貼紙 (跟上一步驟5差不多)
- 移動順序
- 下載圖片
步驟:加上剪裁範圍→剪裁
先來加上剪裁範圍
const NewYearCanvas = (props)=>{
const [uploadClipPath, setUploadClipPath] = useState('')
//...以上省略
const addClip = ()=>{
//創造一個fabric的圓形
const userClipPath = new fabric.Circle({
top: 0,
left: 0,
radius: 50,
width: 100,
height: 100,
fill: 'rgb(178, 178, 178, 0.4)',
})
//因為我想要正圓形,所以把可以上拉左拉的控制點都弄掉
userClipPath.setControlsVisibility({
mb: false,
ml: false,
mr: false,
mt: false
})
//加到canvas上面
//setActiveObject(userClipPath) -->讓他馬上被選取
canvasModal.add(userClipPath).setActiveObject(userClipPath).renderAll();
//存到state裡面
setUploadClipPath(userClipPath)
}
//以下省略
}
export default NewYearCanvas
再來剪裁
讓使用者隨意移動位置,或是放大縮小圓裁切
裁切是利用 Fabric的clipPath參數,製作剪裁遮色片
clipPath的top & left 計算起點是圖片中心,所以要先計算出他的位置
再次算出我們拉的圓左上角起始點的位置,和被操作後的半徑
這邊難點在於計算圖片和剪裁圓形 放大縮小後的範圍,
1.算出圖片中心點 2. 算出剪裁圓的左上角
這邊直接說明:
const NewYearCanvas = (props)=>{
//...以上省略
const clipImage = ()=>{
//算出圖片中心點起點相對於Canvas座標
//getBoundingRect()取得相對於Canvas位置,但圖片都靠左上,就是top0,left0啦
//圖片貼上canvas時,我有把他sacale to canvas的大小,
//所以寬度/高度要乘上scale比例後才是正確的寬度高度,然後中心點就是寬高的一半
const imageCenter = {
top: uploadImage.getBoundingRect().top + uploadImage.height * uploadImage.scaleY / 2,
left: uploadImage.getBoundingRect().left + uploadImage.width * uploadImage.scaleX / 2,
}
//stae裡面存好的上傳圖片直接拿出來用,設定剪裁參數
//radius:(半徑剪裁圓的半徑*放大縮小的scale參數) 除以圖片放大縮小參數
//算出剪裁圓起點的位置(相對於圖片中心)
// 舉top(y座標)為例子:(剪裁圓對Canvas座標y - 圖片中心點座標)/(上傳圖片的縮放比例)
uploadImage.set({
clipPath: new fabric.Circle({
radius: uploadClipPath.radius* uploadClipPath.scaleX / uploadImage.scaleX,
top: (uploadClipPath.getBoundingRect().top - imageCenter
.top) / uploadImage.scaleY,
left: (uploadClipPath.getBoundingRect().left -
imageCenter
.left) / uploadImage.scaleX,
}),
});
canvasModal.renderAll()
}
//以下省略
}
export default NewYearCanvas
如果說明不夠清楚歡迎留言!
另外我是參考這位大大的文章:
Day 26 - Fabricjs 進階自訂控制項
https://ithelp.ithome.com.tw/articles/10209056
把圖片裁切好之後,想要直接加上到Canvas上,
但問題來了,圖片被裁切掉的地方也會以透明方式出現,很難操作
我自己想到的方式是把在Modal上的Canvas寬高都變成跟剪裁的範圍一樣
先把圖片弄到左上角,再直接Canvas換寬度,就不用算圖片剪裁厚的座標啦
然後轉成圖片檔案再加上去底層的Canvas
const addPhoto = (stickerSrc)=>{
//先檢查是否有被剪裁
if (uploadClipPath) {
//往左上角移動
//移動距離就是剪裁圖片的座標,要負的
uploadImage.set({
top: -uploadClipPath.getBoundingRect().top,
left: -uploadClipPath.getBoundingRect().left
})
canvasModal.renderAll()
//置換Canvas的寬高
canvasModal.setDimensions({
width: uploadClipPath.getBoundingRect().width,
height: uploadClipPath.getBoundingRect().height
});
}
//把Canvas轉成圖片格式
const modifiedImage = canvasModal.toDataURL("image/png").replace("image/png",
"image/octet-stream");
//下面把圖片貼上去底層的Canvas(存在state裡面)
const pasteImage = new Image();
pasteImage.src = modifiedImage;
pasteImage.onload = function () {
const image = new fabric.Image(pasteImage);
image.set({
left: 100, //這邊隨意設定
top: 60,
});
canvas.add(image).setActiveObject(image).renderAll();
}
}
//以下省略
}
export default NewYearCanvas
貼紙一樣用fabric創造一個image然後貼上去
移動前後順序才不會有時候虎帽跑到人下面去啦
用fabric的函數: bringToFront()
& sendToBack()
//圖片點擊就傳src回去
<img onClick={()=>addSticker(image.src)}/>
//添加貼紙
const addSticker = ()=>{
const pasteImage = new Image();
pasteImage.src = stickerSrc;
pasteImage.onload = function () {
const image = new fabric.Image(pasteImage);
image.set({
left: 100,
top: 60,
});
canvas.add(image).setActiveObject(image).renderAll();
}
//移動順序
const setOrder = (order)=>{
//取得正被選取的物件
const obj = canvas.getActiveObject()
if(!obj) return
if(order === 'top') obj.bringToFront()
if(order === 'bottom') obj.sendToBack();
}
把Canvas弄成圖片檔案,大功告成!
const output = ()=>{
var image = canvas.toDataURL("image/png").replace("image/png",
"image/octet-stream"
);
const a = document.createElement('a')
a.href = image
a.download = `newyear2020.jpeg`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
原始碼放在這邊:https://github.com/rachel-liaw/react_canvas
不過原始碼因為整個操作流程,會比文章多很多邏輯(多了button 開關、DOM邏輯等等...)
還菜菜的!
有術語上的錯誤、資料結構和寫法有任何建議請不吝指教!
過年後,就要把它變成我的快速圖片製造機(?)
再來加上繪圖功能、拖曳功能......(碎碎念)