虎你快乐啦!自己的新年图自己做 (React+Fabric.js) -上

老妹每年都会帮妈妈画春联,
今年人在国外,拍胸脯保证说会画电子档给她!

呕心沥血画了两天後,妈很满意,但画了两天就只有这样也太亏了吧?
(手画春联我可是不用5分钟一张)
於是乎自己用React+Fabric.js做了这个新年图制作机~
可以快速把亲朋好友图片上传,当Line贺年用啦

下面这不是我妈是网路素人
虎你快乐啦

去年就想玩fabric.js长辈图制作,就沿用去年已经用bootstrap写好的版型

fabric.js是一个Canvas函式库,可以让Canvas上的物件产生互动

然後练习一下刚学会的React,新手上路,请多多包涵

浏览器中文的话会切回中文
https://ithelp.ithome.com.tw/upload/images/20220202/20140247ejjp4Nq4Rk.png

很贴心画了三款,还可以变身成老虎招财猫
还没有新年图的可以到下面玩玩看~(初二都要过了不需要了吧!)

虎你快乐新年图制作机

目前遇到的问题&没有的功能:(有时间再解决...)

-手机版动不了!(Fabric.js无法拖拉图片动不了)
-第一次进画面好像图片无法拖拉动不了(原因不明)重新整理後就可以
-即时Resize暂时无法即时侦测,不确定该怎麽设计各个canvas大小...所以目前都固定,只有手机和电脑板尺寸

(还有bug就先抱歉了?)

因为React+Fabric.js网路上资料有限(Fabric.js的官方文件又太好读了呵呵)
自己查找资料,想梳理一下自己的过程,也想看看有没有前辈可以给一些意见 > <
说明一下自己的开发内容:

主要元件

  1. Canvas 本体,主要操作逻辑 //NewYearCanvas.js档案
  2. 剪裁图片的弹跳视窗 //CustomModal档案

开发顺序

  1. React & Bootstrap & Fabric.js 起手式
  2. 设定 Canvas 背景
    a. 读取src
    b. 设定长宽
  3. 上传图片
    a. Modal开启
    b. 上传图片&读取
  4. 剪裁图片
    a. 添加Clip Path
    b. 裁切
  5. 添加裁切好图片到Canvas上
  6. 添加贴纸 (跟上一步骤5差不多)
  7. 移动顺序
  8. 下载图片

前言写太长了,这一篇我只会写到3
/images/emoticon/emoticon37.gif


1. React & Bootstrap & Fabric.js 起手式

下载要用的东西

//已经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

2. 设定Canvas背景

为了不让底图也可以修改,造成图层太多太复杂,决定把自己的图档当成Canvas的背景
很像图片编辑软件Template的概念
右上方点击图片之後就可以换成背景图~

https://ithelp.ithome.com.tw/upload/images/20220202/20140247ejjp4Nq4Rk.png

利用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

3. 上传图片

上传图片主要是为了让我的亲朋好友们可以上传自己的图像,
然後剪成圆形,还可以带我画的虎帽!

为了不让本来的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

真的还菜菜的!
有术语上的错误、资料结构和写法有任何建议请不吝指教!

/images/emoticon/emoticon41.gif

文章不会写到太多细节,原始码放在这边:https://github.com/rachel-liaw/react_canvas
下篇不知何时会写...(远目)
大家新年快乐!?


<<:  DE2_115(DAY1)以niosii去控制板子上的led灯

>>:  DE2_115(DAY2)用niosii和switch还有NiosII console去控制板子上的led

[Day 17] Sass - Parent Selector

& 父选择器“&”通常与Sass的Nesting用法搭配使用, 当内层的选择器使用&...

【Day15】状态机的撰写

什麽是状态机呢? 状态机,其实是有限状态机(finite-state machine(FSM))的简...

Day 14 Azure cognitive service: Text-to-Speech- Azure 念给你听

Azure cognitive service: Text-to-Speech- Azure 念给你...

Day 0xB UVa948 Fibonaccimal Base

Virtual Judge ZeroJudge 题意 输入十进位的数字,输出对应的费氏进位表示法 ...

Day 06:我们未来再相见

前言一 因为一些原因,今天应该是我铁人赛的完赛日。完成了 20%,剩下的 80%,就留给大家去阅读更...