老妹每年都會幫媽媽畫春聯,
今年人在國外,拍胸脯保證說會畫電子檔給她!
嘔心瀝血畫了兩天後,媽很滿意,但畫了兩天就只有這樣也太虧了吧?
(手畫春聯我可是不用5分鐘一張)
於是乎自己用React+Fabric.js做了這個新年圖製作機~
可以快速把親朋好友圖片上傳,當Line賀年用啦
下面這不是我媽是網路素人
去年就想玩fabric.js
長輩圖製作,就沿用去年已經用bootstrap寫好的版型
fabric.js是一個Canvas函式庫,可以讓Canvas上的物件產生互動
然後練習一下剛學會的React,新手上路,請多多包涵
瀏覽器中文的話會切回中文
很貼心畫了三款,還可以變身成老虎招財貓
還沒有新年圖的可以到下面玩玩看~(初二都要過了不需要了吧!)
目前遇到的問題&沒有的功能:(有時間再解決...)
-手機版動不了!(Fabric.js無法拖拉圖片動不了)
-第一次進畫面好像圖片無法拖拉動不了(原因不明)重新整理後就可以
-即時Resize暫時無法即時偵測,不確定該怎麼設計各個canvas大小...所以目前都固定,只有手機和電腦板尺寸(還有bug就先抱歉了?)
因為React+Fabric.js網路上資料有限(Fabric.js的官方文件又太好讀了呵呵)
自己查找資料,想梳理一下自己的過程,也想看看有沒有前輩可以給一些意見 > <
說明一下自己的開發內容:
主要元件
- Canvas 本體,主要操作邏輯 //NewYearCanvas.js檔案
- 剪裁圖片的彈跳視窗 //CustomModal檔案
開發順序
- React & Bootstrap & Fabric.js 起手式
- 設定 Canvas 背景
a. 讀取src
b. 設定長寬- 上傳圖片
a. Modal開啟
b. 上傳圖片&讀取- 剪裁圖片
a. 添加Clip Path
b. 裁切- 添加裁切好圖片到Canvas上
- 添加貼紙 (跟上一步驟5差不多)
- 移動順序
- 下載圖片
前言寫太長了,這一篇我只會寫到3
下載要用的東西
//已經create-react-app
npm install --save bootstrap //使用Bootstrap樣式
npm install --save react-bootstrap //要使用bootstrap的元件
npm install fabric
在主要的檔案裡面引用
//NewYearCanvas.js檔案
import React, {useEffect, useState } from "react";
import { fabric } from "fabric";
import 'bootstrap/dist/css/bootstrap.min.css';
import './NewYearCanvas.css'; //客製化樣式要蓋過Bootstrap
import CustomModal from "./CustomModal"; //後面上傳用的Modal
import {Form} from "react-bootstrap"; //上傳圖片要用的組件
起手式
使用useEffect Hook, 在第一次渲染時執行Fabic & Canvas的起手式
//NewYearCanvas.js
const NewYearCanvas = (props)=>{
const [canvas, setCanvas] = useState('')
useEffect(()=>{
let canvasWidth = 500;
//new fabric.Canvas('你設的canvas id')
//要正方形的圖片
const canvas = new fabric.Canvas('canvas', {
width: canvasWidth,
height: canvasWidth
})
//將Canvas存在state裡面
setCanvas(canvas)
}, [])
}
export default NewYearCanvas
為了不讓底圖也可以修改,造成圖層太多太複雜,決定把自己的圖檔當成Canvas的背景
很像圖片編輯軟體Template的概念
右上方點擊圖片之後就可以換成背景圖~
利用Fabirc的 canvas.setBackgroundImage()
函式設定背景
//NewYearCanvas.js
const NewYearCanvas = (props)=>{
//部份省略
//圖片src的部分我是從App.js用props傳進來的
const renderBgImages = ()=>{
return props.bgImages.map(image=>{
return (
<img onClick={()=>setBg(image.src)}
role="button"
key={image.alt}
src={image.src}
alt={image.alt} />
)
})
}
//點擊就把src變成參數丟進來
//fabric.Image 為fabric添加圖片的用法
//設成背景:canvas.setBackgroundImage(img)
const setBg = (src)=>{
if(!canvas) return
fabric.Image.fromURL(src, function (img) {
img.scaleToWidth(canvas.width); //最後設寬度
canvas.setBackgroundImage(img);
canvas.requestRenderAll();
});
}
return (
//...部份省略
<div>
<canvas id="canvas"></canvas>
</div>
<div>{renderBgImages()}</div>
//部份省略
)
}
export default NewYearCanvas
上傳圖片主要是為了讓我的親朋好友們可以上傳自己的圖像,
然後剪成圓形,還可以帶我畫的虎帽!
為了不讓本來的Canvas太複雜,另開彈跳視窗讓使用者上傳圖片~
另做了一個組件:CustomModal.js 使用Boostrap Modal元件
細節部分(標題等等)都用props
傳進來
//CustomModal.js
import Reactfrom "react";
import {Modal} from "react-bootstrap";
const CustomModal = (props)=>{
return(
<Modal size={props.size} show={props.show} onHide={props.handleClose}>
<Modal.Header closeButton>
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<Modal.Body style={{overflow: 'scroll'}}>
{props.children}
</Modal.Body>
<Modal.Footer>
<button onClick={props.resetModal} type="button" className="btn btn-secondary"
data-bs-dismiss="modal">{props.resetText}</button>
{renderSaveButton()}
</Modal.Footer>
</Modal>
)
}
export default CustomModal
上傳檔案也使用bootstrap form
元件,變成props的小孩塞到CustomModal.js裡面~
欸等等,Modal用的canvas也要初始化啊!
既然都在NewYearCanvas.js就一樣使用useEffect初始化他
然後也用state存起來方便等一下呼叫!
//NewYearCanvas.js
import CustomModal from "./CustomModal";
const NewYearCanvas = (props)=>{
//...以上省略
const [canvasModal, setCanvasModal] = useState('')
useEffect(()=>{
const canvasWidth = 500;
const canvasModal = new fabric.Canvas('canvasModal', {
width: canvasWidth,
height: canvasWidth,
})
setCanvasModal(canvasModal)
},[show])
//中間省略
return (
//props太長了省略不寫
<CustomModal
size="lg">
//上傳檔案部分
<Form.Group onChange={uploadPhoto}>
//只接受圖片類型
<Form.Control type="file" id="imageUpload" accept="image/*"/>
</Form.Group>
//modal用的canvasModal, id要不一樣
<canvas id="canvasModal" className="mx-auto"></canvas>
</CustomModal>
)
}
export default NewYearCanvas
然後監聽onChange,呼叫uploadPhoto 去處理上傳的圖片檔案
這張圖片呢,主要是給大家切割用,照理說應該放成背景圖的樣子,
可是背景圖又不能切割!所以還是做成fabric.js的圖片檔案,但讓他不能選取、
不能移動、啥都不能做!所以在做圖片時,把hasControls等參數設定為false
//NewYearCanvas.js
import CustomModal from "./CustomModal";
const NewYearCanvas = (props)=>{
const [uploadImage, setUploadImage] = useState('')
//...以上省略
const uploadPhoto = (e)=>{
//創建一個圖片並且存取上傳的檔案的src
const uploadImageTmp = new Image();
uploadImageTmp.src = URL.createObjectURL(e.target.files[0]);
uploadImageTmp.onload = function () {
//做一個fabric Image物件,並設定參數
const image = new fabric.Image(uploadImageTmp);
image.set({
left: 0,
top: 0,
clipPath: '',
hasControls: false, //圖片我不想讓他可以變形會動
lockMovementX: true,//圖片也不能移動
lockMovementY: true,
"selectable": false,//圖片也不能選取
"evented": false//圖片不成任何事件的目標
});
//先把圖片設成Canvas的寬度
//再把Canvas的圖片變跟圖形一樣高
//這是我個人喜好啦!但這樣圖片會變小
image.scaleToWidth(canvasModal.width);
canvasModal.setHeight(image.height*image.scaleY)
//一樣存在state裡面
setUploadImage(image)
//把圖片加進去
canvasModal.add(image).renderAll();
}
}
}
export default NewYearCanvas
真的還菜菜的!
有術語上的錯誤、資料結構和寫法有任何建議請不吝指教!
文章不會寫到太多細節,原始碼放在這邊:https://github.com/rachel-liaw/react_canvas
下篇不知何時會寫...(遠目)
大家新年快樂!?