虎你发财啦!自己的新年图自己做 (React+Fabric.js) -下

前情提要

为了不浪费我白白画的春联,做了一个新年图制造机
还没有新年图的可以到下面玩玩看~(快收假了不需要了吧!为什麽我的GA没有数据QQ)

虎你快乐新年图制作机

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

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

(还有bug就先抱歉了?)

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

开发内容:

主要元件

  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. 下载图片

4. 剪裁图片

步骤:加上剪裁范围→剪裁
先来加上剪裁范围


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. 算出剪裁圆的左上角

https://ithelp.ithome.com.tw/upload/images/20220204/20140247Uh7KcgB7wK.png

这边直接说明:

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



5. 添加切好图片到Canvas上

把图片裁切好之後,想要直接加上到Canvas上,

但问题来了,图片被裁切掉的地方也会以透明方式出现,很难操作

https://ithelp.ithome.com.tw/upload/images/20220204/20140247fpVLNLEZZ8.png

我自己想到的方式是把在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

6. 添加贴纸 & 7. 移动顺序

贴纸一样用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();

    }


8.下载图片

把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逻辑等等...)

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

/images/emoticon/emoticon41.gif

过年後,就要把它变成我的快速图片制造机(?)
再来加上绘图功能、拖曳功能......(碎碎念)


<<:  Jupyter Notebook 输入栏位设计(2)

>>:  2. 编辑器的使用&创建第一个网站

Day 1 念完了还是不会

A lie would have no sense unless the truth were f...

[Day30] AWS Elastic Load Balancing (ELB)

Elastic Load Balancing 可在多个目标 (例如 Amazon EC2 执行个体、...

[面试][资料库]关联式资料库要如何设计避免超卖?

库存只剩 1 件,但却有 10 个人买到? 网路商城特卖会常常会推出特定商品限量 1 组的抢购活动...

Day 6 - Function 时空旅行 (1) - 参数优化

前言 Array 跟 Object 两兄弟的故事告一段落了,接着是 Object 在外面养(?)的另...

Day4 WordPress 介绍,基础设定与发文

上篇文章我们在 BlueHost 架起了 WordPress 环境,但也许你还不知道什麽是 Word...