*新闻详情页*/>
日期:2020-12-11 类型:科技新闻 我要分享
关键词:ps网页版在线制作,静态网页制作方法,微云网页版,怎么制作一个网页,django网页模板
哈哈哈俺又来啦,这次带来的是canvas完成1些画布作用的文章内容,期待大伙儿喜爱!
序言
由于也是大3了,近期俺也在找实习,以前有1个自身的小新项目:
https://github.com/zhcxk1998/School-Partners
招聘面试官说能够往深层次次思索1下,也许加1些新的作用来提升新项目的难度,他提了几个提议,在其中1个便是 试卷线上审阅,老师能够在上应对工作开展注释,圈圈点点等 俺当天夜里就刚开始科学研究这个东东哈哈哈,终究被我科学研究出来啦!
选用的是 canvas
绘图画笔,由css3的 transform
特性来开展平移与放缩,以后再详尽详细介绍详细介绍
(期待大伙儿能够留下珍贵的赞与star嘻嘻)
实际效果预览
动图是放cdn的,假如浏览不上,能够登陆线上尝试尝试: test.algbb.cn/#/admin/con…
公式推导 假如不想看公式怎样推导,能够立即绕过看后边的实际完成~ 1. 座标变换公式 变换公式详细介绍
实际上1刚开始也是想在网络上找1下有木有有关的材料,可是可是找不到,因此就自身渐渐地的推出来了。我就举1下横座标的事例吧!
通用性公式
这个公式是表明,根据公式来将电脑鼠标按下的座标变换为画布中的相对性座标,这1点尤其关键
(transformOrigin - downX) / scale * (scale⑴) + downX - translateX = pointX
主要参数解释
transformOrigin: transform转变的基点(根据这个特性来操纵元素以哪里开展转变)
downX: 电脑鼠标按下的座标(留意,用的情况下必须减去器皿左偏位间距,由于大家要的是相对器皿的座标)
scale: 放缩倍数,默认设置为1
translateX: 平移的间距
推导全过程
这个公式的话,实际上就较为通用性,能够用在其他运用到 transform
特性的情景,至于如何推导的话,我是用的笨方法
实际的检测编码,放在文末,必须自取~
1. 先做出两个同样的元素,随后标识上座标,而且设定器皿特性 overflow:hidden
来掩藏外溢內容
ok,如今就有两个1样的引流矩阵啦,大家为他标识上1些红点,随后大家对左侧的开展css3的款式转变 transform
矩形框的宽高是 360px * 360px
的,大家界定1下他的转变特性,转变基点挑选正管理中心,变大3倍
// css transform-origin: 180px 180px; transform: scale(3, 3);
获得以下結果
ok,大家如今比照1下上面的結果,就会发现,变大3倍的情况下,正好是正中间黑色方块占有了所有宽度。接下来大家便可以对这些点与本来沒有开展转变(右侧)的矩形框开展比照便可以获得她们座标的关联啦
2. 刚开始对两个座标开展比照,随后推出公式
如今举1个简易的事例吧,比如大家算1下左上角的座标(如今早已标识为黄色了)
实际上大家实际上便可以立即心算出来座标的关联啦
( 这里左侧测算座标的值是大家电脑鼠标按下的座标 )
( 这里左侧测算座标的值是大家电脑鼠标按下的座标 )
( 这里左侧测算座标的值是大家电脑鼠标按下的座标 )
360px
,因此分为3等份,每份宽度是 120px
x:120 y:0
,右侧的黄色标识为 x:160 y:120
(这个实际上肉眼看应当就可以看出来了,确实不好能够用纸笔算1算)这个座标将会有点独特,大家再换几个来测算测算(依据独特推1般)
蓝色标识:左侧: x:120 y:120
,右侧: x: 160 y:160
翠绿色标识:左侧: x: 240 y:240
,右侧: x: 200: y:200
好了,大家类似早已能够拿到座标之间的关联了,大家能够列1个表
还感觉不安心?大家能够换1下,放缩倍数与器皿宽高开展测算
不知道道大伙儿有木有觉得呢,随后大家便可以渐渐地依据座标推出通用性的公式啦
(transformOrigin - downX) / scale * (scale⑴) + down - translateX = point
自然,大家也许也有这个 translateX
沒有尝试,这个就较为简易1点了,脑内仿真模拟1下,就了解大家能够减去位移的间距就ok啦。大家检测1下
大家先改动1下款式,新增1下位移的间距
transform-origin: 180px 180px; transform: scale(3, 3) translate(⑷0px,⑷0px);
還是大家上面的情况,ok,大家如今蓝色跟翠绿色的标识還是11对应的,那大家看看如今的座标状况
x:0 y:0
,右侧: x:160 y:160
x:120 y:120
,右侧: x:200 y:200
大家各自应用公式算1下出来的座标是如何的 (下列为历经座标换算)
蓝色:左侧: x:120 y:120
,右侧: x:160 y:160
翠绿色:左侧: x:160 y:160
,右侧: x:200 y:200
不难发现,大家实际上就相差了与位移间距 translateX/translateY
的差值,因此,大家只必须减去位移的间距便可以完善的开展座标变换啦
检测公式
依据上面的公式,大家能够简易检测1下!这个公式究竟能不可以起效!!!
大家立即延用上面的demo,检测1下假如元素开展了转变,大家电脑鼠标点下的地区转化成1个标识,部位是不是显示信息正确。看起来很ok啊(手动式搞笑)
const wrap = document.getElementById('wrap') wrap.onmousedown = function (e) { const downX = e.pageX - wrap.offsetLeft const downY = e.pageY - wrap.offsetTop const scale = 3 const translateX = ⑷0 const translateY = ⑷0 const transformOriginX = 180 const transformOriginY = 180 const dot = document.getElementById('dot') dot.style.left = (transformOriginX - downX) / scale * (scale - 1) + downX - translateX + 'px' dot.style.top = (transformOriginY - downY) / scale * (scale - 1) + downY - translateY + 'px' }
将会有人会问,为何要减去这个 offsetLeft
跟 offsetTop
呢,由于大家上面不断强调,大家测算的是电脑鼠标点一下的座标,而这个座标還是相对大家展现器皿的座标,因此大家要减去器皿自身的偏位量才行。
组件设计方案
既然demo啥的都早已检测了ok了,大家接下来就逐1剖析1下这个组件应当咋设计方案好呢(现阶段仍为低配版,以后再开展提升健全)
1. 基础的画布组成
大家先简易剖析1下这个组成吧,实际上关键便是1个画布的器皿,右侧1个专用工具栏,仅此罢了
大致就这模样啦!
<div className="mark-paper__wrap" ref={wrapRef}> <canvas ref={canvasRef} className="mark-paper__canvas"> <p>很可是,这个东东与您的电脑上不配!</p> </canvas> <div className="mark-paper__sider" /> </div>
大家唯1必须的1点便是,器皿必须设定特性 overflow: hidden
用来掩藏內部canvas画布外溢的內容,也便是说,大家要操纵大家可视性的地区。另外大家必须动态性获得器皿宽高来为canvas设定规格
2. 原始化canvas画布与填充照片
大家能够弄个方式来原始化而且填充画布,下列截取关键一部分,实际上便是为canvas画布设定规格与填充大家的照片
const fillImage = async () => { // 此处省略... const img: HTMLImageElement = new Image() img.src = await getURLBase64(fillImageSrc) img.onload = () => { canvas.width = img.width canvas.height = img.height context.drawImage(img, 0, 0) // 设定转变基点,为画布器皿中间 canvas.style.transformOrigin = `${wrap?.offsetWidth / 2}px ${wrap?.offsetHeight / 2}px` // 消除上1次转变的实际效果 canvas.style.transform = '' } }
3. 监视canvas画布的各种各样电脑鼠标恶性事件
这个操纵挪动的话,大家最先能够弄1个方式来监视画布电脑鼠标的各种各样恶性事件,能够区别不一样的方式来开展不一样的恶性事件解决
const handleCanvas = () => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!context || !wrap) return // 消除上1次设定的监视,防止获得主要参数不正确 wrap.onmousedown = null wrap.onmousedown = function (event: MouseEvent) { const downX: number = event.pageX const downY: number = event.pageY // 区别大家如今挑选的电脑鼠标方式:挪动、画笔、橡皮擦 switch (mouseMode) { case MOVE_MODE: handleMoveMode(downX, downY) break case LINE_MODE: handleLineMode(downX, downY) break case ERASER_MODE: handleEraserMode(downX, downY) break default: break } }
4. 完成画布挪动
这个就较为好办啦,大家只必须运用电脑鼠标按下的座标,和大家拖拽的间距便可以完成画布的挪动啦,由于涉及到到每次挪动都必须测算全新的位移间距,大家能够界定几个自变量来开展测算。
这里监视的是器皿的电脑鼠标恶性事件,而并不是canvas画布的恶性事件,由于这模样大家能够再挪动超出界限的情况下还可以开展挪动实际操作
简易的总结1下:
// 界定1些自变量,来储存当今/全新的挪动情况 // 当今位移的间距 const translatePointXRef: MutableRefObject<number> = useRef(0) const translatePointYRef: MutableRefObject<number> = useRef(0) // 上1次位移完毕的位移间距 const fillStartPointXRef: MutableRefObject<number> = useRef(0) const fillStartPointYRef: MutableRefObject<number> = useRef(0) // 挪动情况下的监视涵数 const handleMoveMode = (downX: number, downY: number) => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const { current: fillStartPointX } = fillStartPointXRef const { current: fillStartPointY } = fillStartPointYRef if (!canvas || !wrap || mouseMode !== 0) return // 为器皿加上挪动恶性事件,能够在空白处挪动照片 wrap.onmousemove = (event: MouseEvent) => { const moveX: number = event.pageX const moveY: number = event.pageY // 升级如今的位移间距,值为:上1次位移完毕的座标+挪动的间距 translatePointXRef.current = fillStartPointX + (moveX - downX) translatePointYRef.current = fillStartPointY + (moveY - downY) // 升级画布的css转变 canvas.style.transform = `scale(${canvasScale},${canvasScale}) translate(${translatePointXRef.current}px,${translatePointYRef.current}px)` } wrap.onmouseup = (event: MouseEvent) => { const upX: number = event.pageX const upY: number = event.pageY // 撤销恶性事件监视 wrap.onmousemove = null wrap.onmouseup = null; // 电脑鼠标抬起情况下,升级“上1次唯1完毕的座标” fillStartPointXRef.current = fillStartPointX + (upX - downX) fillStartPointYRef.current = fillStartPointY + (upY - downY) } }
5. 完成画布放缩
画布放缩我关键根据右边的拖动条和电脑鼠标滚轮来完成,最先大家再监视画布电脑鼠标恶性事件的涵数中加1下监视滚轮的恶性事件
总结1下:
// 监视电脑鼠标滚轮,升级画布放缩倍数 const handleCanvas = () => { const { current: wrap } = wrapRef // 省略1万字... wrap.onwheel = null wrap.onwheel = (e: MouseWheelEvent) => { const { deltaY } = e // 这里要留意1下,我是0.1来递增下降,可是由于JS应用IEEE 754,来测算,因此精度有难题,大家自身解决1下 const newScale: number = deltaY > 0 ? (canvasScale * 10 - 0.1 * 10) / 10 : (canvasScale * 10 + 0.1 * 10) / 10 if (newScale < 0.1 || newScale > 2) return setCanvasScale(newScale) } } // 监视拖动条来操纵放缩 <Slider min={0.1} max={2.01} step={0.1} value={canvasScale} tipFormatter={(value) => `${(value).toFixed(2)}x`} onChange={handleScaleChange} /> const handleScaleChange = (value: number) => { setCanvasScale(value) }
接着大家应用hooks的不良反应涵数,依靠于画布放缩倍数来开展款式的升级
//监视放缩画布 useEffect(() => { const { current: canvas } = canvasRef const { current: translatePointX } = translatePointXRef const { current: translatePointY } = translatePointYRef canvas && (canvas.style.transform = `scale(${canvasScale},${canvasScale}) translate(${translatePointX}px,${translatePointY}px)`) }, [canvasScale])
6. 完成画笔绘图
这个就必须用到大家以前推导出来来的公式啦!由于呢,细心想1下,假如大家放缩位移以后,大家电脑鼠标按下的部位,他的座标将会就相对画布来讲会有转变, 因此大家必须变换1下才可以开展电脑鼠标按下的部位与画布的部位11对应的实际效果
略微总结1下:
// 运用公式变换1下座标 const generateLinePoint = (x: number, y: number) => { const { current: wrap } = wrapRef const { current: translatePointX } = translatePointXRef const { current: translatePointY } = translatePointYRef const wrapWidth: number = wrap?.offsetWidth || 0 const wrapHeight: number = wrap?.offsetHeight || 0 // 放缩位移座标转变规律性 // (transformOrigin - downX) / scale * (scale⑴) + downX - translateX = pointX const pointX: number = ((wrapWidth / 2) - x) / canvasScale * (canvasScale - 1) + x - translatePointX const pointY: number = ((wrapHeight / 2) - y) / canvasScale * (canvasScale - 1) + y - translatePointY return { pointX, pointY } } // 监视电脑鼠标画笔恶性事件 const handleLineMode = (downX: number, downY: number) => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !wrap || !context) return const offsetLeft: number = canvas.offsetLeft const offsetTop: number = canvas.offsetTop // 减去画布偏位的间距(以画布为标准开展测算座标) downX = downX - offsetLeft downY = downY - offsetTop const { pointX, pointY } = generateLinePoint(downX, downY) context.globalCompositeOperation = "source-over" context.beginPath() // 设定画笔起始点 context.moveTo(pointX, pointY) canvas.onmousemove = null canvas.onmousemove = (event: MouseEvent) => { const moveX: number = event.pageX - offsetLeft const moveY: number = event.pageY - offsetTop const { pointX, pointY } = generateLinePoint(moveX, moveY) // 刚开始绘图画笔线条~ context.lineTo(pointX, pointY) context.stroke() } canvas.onmouseup = () => { context.closePath() canvas.onmousemove = null canvas.onmouseup = null } }
7. 橡皮擦的完成
橡皮擦现阶段也有点难题,如今的话是根据将 canvas
画布的情况照片 + globalCompositeOperation
这个特性来仿真模拟橡皮擦的完成,但是,这时候候照片转化成出来以后,橡皮擦的痕迹会变为白色,而并不是全透明
此流程与画笔完成类似,仅有1点点小变化
设定特性 context.globalCompositeOperation = "destination-out"
// 现阶段橡皮擦也有点难题,前端开发显示信息一切正常,储存照片下来,擦除的痕迹会变为白色 const handleEraserMode = (downX: number, downY: number) => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !wrap || !context) return const offsetLeft: number = canvas.offsetLeft const offsetTop: number = canvas.offsetTop downX = downX - offsetLeft downY = downY - offsetTop const { pointX, pointY } = generateLinePoint(downX, downY) context.beginPath() context.moveTo(pointX, pointY) canvas.onmousemove = null canvas.onmousemove = (event: MouseEvent) => { const moveX: number = event.pageX - offsetLeft const moveY: number = event.pageY - offsetTop const { pointX, pointY } = generateLinePoint(moveX, moveY) context.globalCompositeOperation = "destination-out" context.lineWidth = lineWidth context.lineTo(pointX, pointY) context.stroke() } canvas.onmouseup = () => { context.closePath() canvas.onmousemove = null canvas.onmouseup = null } }
8. 撤消与修复的作用完成
这个的话,大家最先必须掌握普遍的撤消与修复的作用的逻辑性 分几种状况吧
画布情况的升级
因此大家必须设定1些自变量来存,情况目录,与当今画笔的情况下标
// 界定主要参数存东东 const canvasHistroyListRef: MutableRefObject<ImageData[]> = useRef([]) const [canvasCurrentHistory, setCanvasCurrentHistory] = useState<number>(0)
大家还必须在原始化canvas的情况下,大家就加上入当今的情况存入目录中,做为最开始刚开始的空画布情况
const fillImage = async () => { // 省略1万字... img.src = await getURLBase64(fillImageSrc) img.onload = () => { const imageData: ImageData = context.getImageData(0, 0, canvas.width, canvas.height) canvasHistroyListRef.current = [] canvasHistroyListRef.current.push(imageData) setCanvasCurrentHistory(1) } }
随后大家就完成1下,画笔升级情况下,大家也必须将当今的情况加上入 画笔情况目录 ,而且升级当今情况对应的下标,还必须解决1下1些细节
总结1下:
const handleLineMode = (downX: number, downY: number) => { // 省略1万字... canvas.onmouseup = () => { const imageData: ImageData = context.getImageData(0, 0, canvas.width, canvas.height) // 假如此时处在撤消情况,此时再应用画笔,则将以后的情况清空,以刚画的做为全新的画布情况 if (canvasCurrentHistory < canvasHistroyListRef.current.length) { canvasHistroyListRef.current = canvasHistroyListRef.current.slice(0, canvasCurrentHistory) } canvasHistroyListRef.current.push(imageData) setCanvasCurrentHistory(canvasCurrentHistory + 1) context.closePath() canvas.onmousemove = null canvas.onmouseup = null } }
画布情况的撤消与修复
ok,实际上如今有关画布情况的升级,大家早已进行了。接下来大家必须解决1下情况的撤消与修复的作用啦
大家先界定1下这个专用工具栏吧
随后大家设定对应的恶性事件,各自是撤消,修复,与清空,实际上都很非常容易看懂,数最多便是解决1下界限状况。
const handleRollBack = () => { const isFirstHistory: boolean = canvasCurrentHistory === 1 if (isFirstHistory) return setCanvasCurrentHistory(canvasCurrentHistory - 1) } const handleRollForward = () => { const { current: canvasHistroyList } = canvasHistroyListRef const isLastHistory: boolean = canvasCurrentHistory === canvasHistroyList.length if (isLastHistory) return setCanvasCurrentHistory(canvasCurrentHistory + 1) } const handleClearCanvasClick = () => { const { current: canvas } = canvasRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !context || canvasCurrentHistory === 0) return // 清空画布历史时间 canvasHistroyListRef.current = [canvasHistroyListRef.current[0]] setCanvasCurrentHistory(1) message.success('画布消除取得成功!') }
恶性事件设定好以后,大家便可以刚开始监视1下这个 canvasCurrentHistory
当今情况下标,应用不良反应涵数开展解决
useEffect(() => { const { current: canvas } = canvasRef const { current: canvasHistroyList } = canvasHistroyListRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !context || canvasCurrentHistory === 0) return context?.putImageData(canvasHistroyList[canvasCurrentHistory - 1], 0, 0) }, [canvasCurrentHistory])
为canvas画布填充图象信息内容!
这样就大获全胜啦!!!
9. 完成电脑鼠标标志的转变
大家简易的解决1下,画笔方式则是画笔的标志,橡皮擦方式下电脑鼠标是橡皮擦,挪动方式下便是一般的挪动标志
切换方式情况下,设定1下不一样的标志
const handleMouseModeChange = (event: RadioChangeEvent) => { const { target: { value } } = event const { current: canvas } = canvasRef const { current: wrap } = wrapRef setmouseMode(value) if (!canvas || !wrap) return switch (value) { case MOVE_MODE: canvas.style.cursor = 'move' wrap.style.cursor = 'move' break case LINE_MODE: canvas.style.cursor = `url('http://cdn.algbb.cn/pencil.ico') 6 26, pointer` wrap.style.cursor = 'default' break case ERASER_MODE: message.warning('橡皮擦作用并未健全,储存照片会出現不正确') canvas.style.cursor = `url('http://cdn.algbb.cn/eraser.ico') 6 26, pointer` wrap.style.cursor = 'default' break default: canvas.style.cursor = 'default' wrap.style.cursor = 'default' break } }
10. 切换照片
如今的话只是1个demo情况,根据点一下挑选框,切换不一样的照片
// 重设转换主要参数,再次绘图照片 useEffect(() => { setIsLoading(true) translatePointXRef.current = 0 translatePointYRef.current = 0 fillStartPointXRef.current = 0 fillStartPointYRef.current = 0 setCanvasScale(1) fillImage() }, [fillImageSrc]) const handlePaperChange = (value: string) => { const fillImageList = { 'xueshengjia': 'http://cdn.algbb.cn/test/canvasTest.jpg', 'xueshengyi': 'http://cdn.algbb.cn/test/canvasTest2.png', 'xueshengbing': 'http://cdn.algbb.cn/emoji/30.png', } setFillImageSrc(fillImageList[value]) }
留意事项
留意器皿的偏位量
大家必须留意1下,由于公式中的 downX
是相对性器皿的座标,也便是说,大家必须减去器皿的偏位量,这类状况会出現在应用了 margin
等主要参数,或说上方或左边有其他元素的状况
大家輸出1下大家鲜红色的元素的 offsetLeft
等特性,会发现他是早已自身就有50的偏位量了,大家测算电脑鼠标点一下的座标的情况下就要减去这1一部分的偏位量
window.onload = function () { const test = document.getElementById('test') console.log(`offsetLeft: ${test.offsetLeft}, offsetHeight: ${test.offsetTop}`) } html, body { margin: 0; padding: 0; } #test { width: 50px; height: 50px; margin-left: 50px; background: red; } <div class="container"> <div id="test"></div> </div>
留意父组件应用relative相对性合理布局的状况
倘若大家如今有1种这类的合理布局,复印鲜红色元素的偏位量,看起来都挺一切正常的
可是假如大家总体目标元素的父元素(也便是黄色一部分)设定 relative
相对性合理布局
.wrap { position: relative; width: 400px; height: 300px; background: yellow; } <div class="container"> <div class="sider"></div> <div class="wrap"> <div id="test"></div> </div> </div>
这时候候大家复印出来的偏位量会是是多少呢
两次回答不1样啊,由于大家的偏位量是依据相对性部位来测算的,假如父器皿应用相对性合理布局,则会危害大家子元素的偏位量
组件编码(低配版)
import React, { FC, ComponentType, useEffect, useRef, RefObject, useState, MutableRefObject } from 'react' import { CustomBreadcrumb } from '@/admin/components' import { RouteComponentProps } from 'react-router-dom'; import { FormComponentProps } from 'antd/lib/form'; import { Slider, Radio, Button, Tooltip, Icon, Select, Spin, message, Popconfirm } from 'antd'; import './index.scss' import { RadioChangeEvent } from 'antd/lib/radio'; import { getURLBase64 } from '@/admin/utils/getURLBase64' const { Option, OptGroup } = Select; type MarkPaperProps = RouteComponentProps & FormComponentProps const MarkPaper: FC<MarkPaperProps> = (props: MarkPaperProps) => { const MOVE_MODE: number = 0 const LINE_MODE: number = 1 const ERASER_MODE: number = 2 const canvasRef: RefObject<HTMLCanvasElement> = useRef(null) const containerRef: RefObject<HTMLDivElement> = useRef(null) const wrapRef: RefObject<HTMLDivElement> = useRef(null) const translatePointXRef: MutableRefObject<number> = useRef(0) const translatePointYRef: MutableRefObject<number> = useRef(0) const fillStartPointXRef: MutableRefObject<number> = useRef(0) const fillStartPointYRef: MutableRefObject<number> = useRef(0) const canvasHistroyListRef: MutableRefObject<ImageData[]> = useRef([]) const [lineColor, setLineColor] = useState<string>('#fa4b2a') const [fillImageSrc, setFillImageSrc] = useState<string>('') const [mouseMode, setmouseMode] = useState<number>(MOVE_MODE) const [lineWidth, setLineWidth] = useState<number>(5) const [canvasScale, setCanvasScale] = useState<number>(1) const [isLoading, setIsLoading] = useState<boolean>(false) const [canvasCurrentHistory, setCanvasCurrentHistory] = useState<number>(0) useEffect(() => { setFillImageSrc('http://cdn.algbb.cn/test/canvasTest.jpg') }, []) // 重设转换主要参数,再次绘图照片 useEffect(() => { setIsLoading(true) translatePointXRef.current = 0 translatePointYRef.current = 0 fillStartPointXRef.current = 0 fillStartPointYRef.current = 0 setCanvasScale(1) fillImage() }, [fillImageSrc]) // 画布主要参数变化时,再次监视canvas useEffect(() => { handleCanvas() }, [mouseMode, canvasScale, canvasCurrentHistory]) // 监视画笔色调转变 useEffect(() => { const { current: canvas } = canvasRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!context) return context.strokeStyle = lineColor context.lineWidth = lineWidth context.lineJoin = 'round' context.lineCap = 'round' }, [lineWidth, lineColor]) //监视放缩画布 useEffect(() => { const { current: canvas } = canvasRef const { current: translatePointX } = translatePointXRef const { current: translatePointY } = translatePointYRef canvas && (canvas.style.transform = `scale(${canvasScale},${canvasScale}) translate(${translatePointX}px,${translatePointY}px)`) }, [canvasScale]) useEffect(() => { const { current: canvas } = canvasRef const { current: canvasHistroyList } = canvasHistroyListRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !context || canvasCurrentHistory === 0) return context?.putImageData(canvasHistroyList[canvasCurrentHistory - 1], 0, 0) }, [canvasCurrentHistory]) const fillImage = async () => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') const img: HTMLImageElement = new Image() if (!canvas || !wrap || !context) return img.src = await getURLBase64(fillImageSrc) img.onload = () => { // 取正中间3D渲染照片 // const centerX: number = canvas && canvas.width / 2 - img.width / 2 || 0 // const centerY: number = canvas && canvas.height / 2 - img.height / 2 || 0 canvas.width = img.width canvas.height = img.height // 情况设定为照片,橡皮擦的实际效果才可以出来 canvas.style.background = `url(${img.src})` context.drawImage(img, 0, 0) context.strokeStyle = lineColor context.lineWidth = lineWidth context.lineJoin = 'round' context.lineCap = 'round' // 设定转变基点,为画布器皿中间 canvas.style.transformOrigin = `${wrap?.offsetWidth / 2}px ${wrap?.offsetHeight / 2}px` // 消除上1次转变的实际效果 canvas.style.transform = '' const imageData: ImageData = context.getImageData(0, 0, canvas.width, canvas.height) canvasHistroyListRef.current = [] canvasHistroyListRef.current.push(imageData) // canvasCurrentHistoryRef.current = 1 setCanvasCurrentHistory(1) setTimeout(() => { setIsLoading(false) }, 500) } } const generateLinePoint = (x: number, y: number) => { const { current: wrap } = wrapRef const { current: translatePointX } = translatePointXRef const { current: translatePointY } = translatePointYRef const wrapWidth: number = wrap?.offsetWidth || 0 const wrapHeight: number = wrap?.offsetHeight || 0 // 放缩位移座标转变规律性 // (transformOrigin - downX) / scale * (scale⑴) + downX - translateX = pointX const pointX: number = ((wrapWidth / 2) - x) / canvasScale * (canvasScale - 1) + x - translatePointX const pointY: number = ((wrapHeight / 2) - y) / canvasScale * (canvasScale - 1) + y - translatePointY return { pointX, pointY } } const handleLineMode = (downX: number, downY: number) => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !wrap || !context) return const offsetLeft: number = canvas.offsetLeft const offsetTop: number = canvas.offsetTop // 减去画布偏位的间距(以画布为标准开展测算座标) downX = downX - offsetLeft downY = downY - offsetTop const { pointX, pointY } = generateLinePoint(downX, downY) context.globalCompositeOperation = "source-over" context.beginPath() context.moveTo(pointX, pointY) canvas.onmousemove = null canvas.onmousemove = (event: MouseEvent) => { const moveX: number = event.pageX - offsetLeft const moveY: number = event.pageY - offsetTop const { pointX, pointY } = generateLinePoint(moveX, moveY) context.lineTo(pointX, pointY) context.stroke() } canvas.onmouseup = () => { const imageData: ImageData = context.getImageData(0, 0, canvas.width, canvas.height) // 假如此时处在撤消情况,此时再应用画笔,则将以后的情况清空,以刚画的做为全新的画布情况 if (canvasCurrentHistory < canvasHistroyListRef.current.length) { canvasHistroyListRef.current = canvasHistroyListRef.current.slice(0, canvasCurrentHistory) } canvasHistroyListRef.current.push(imageData) setCanvasCurrentHistory(canvasCurrentHistory + 1) context.closePath() canvas.onmousemove = null canvas.onmouseup = null } } const handleMoveMode = (downX: number, downY: number) => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const { current: fillStartPointX } = fillStartPointXRef const { current: fillStartPointY } = fillStartPointYRef if (!canvas || !wrap || mouseMode !== 0) return // 为器皿加上挪动恶性事件,能够在空白处挪动照片 wrap.onmousemove = (event: MouseEvent) => { const moveX: number = event.pageX const moveY: number = event.pageY translatePointXRef.current = fillStartPointX + (moveX - downX) translatePointYRef.current = fillStartPointY + (moveY - downY) canvas.style.transform = `scale(${canvasScale},${canvasScale}) translate(${translatePointXRef.current}px,${translatePointYRef.current}px)` } wrap.onmouseup = (event: MouseEvent) => { const upX: number = event.pageX const upY: number = event.pageY wrap.onmousemove = null wrap.onmouseup = null; fillStartPointXRef.current = fillStartPointX + (upX - downX) fillStartPointYRef.current = fillStartPointY + (upY - downY) } } // 现阶段橡皮擦也有点难题,前端开发显示信息一切正常,储存照片下来,擦除的痕迹会变为白色 const handleEraserMode = (downX: number, downY: number) => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !wrap || !context) return const offsetLeft: number = canvas.offsetLeft const offsetTop: number = canvas.offsetTop downX = downX - offsetLeft downY = downY - offsetTop const { pointX, pointY } = generateLinePoint(downX, downY) context.beginPath() context.moveTo(pointX, pointY) canvas.onmousemove = null canvas.onmousemove = (event: MouseEvent) => { const moveX: number = event.pageX - offsetLeft const moveY: number = event.pageY - offsetTop const { pointX, pointY } = generateLinePoint(moveX, moveY) context.globalCompositeOperation = "destination-out" context.lineWidth = lineWidth context.lineTo(pointX, pointY) context.stroke() } canvas.onmouseup = () => { const imageData: ImageData = context.getImageData(0, 0, canvas.width, canvas.height) if (canvasCurrentHistory < canvasHistroyListRef.current.length) { canvasHistroyListRef.current = canvasHistroyListRef.current.slice(0, canvasCurrentHistory) } canvasHistroyListRef.current.push(imageData) setCanvasCurrentHistory(canvasCurrentHistory + 1) context.closePath() canvas.onmousemove = null canvas.onmouseup = null } } const handleCanvas = () => { const { current: canvas } = canvasRef const { current: wrap } = wrapRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!context || !wrap) return // 消除上1次设定的监视,防止获得主要参数不正确 wrap.onmousedown = null wrap.onmousedown = function (event: MouseEvent) { const downX: number = event.pageX const downY: number = event.pageY switch (mouseMode) { case MOVE_MODE: handleMoveMode(downX, downY) break case LINE_MODE: handleLineMode(downX, downY) break case ERASER_MODE: handleEraserMode(downX, downY) break default: break } } wrap.onwheel = null wrap.onwheel = (e: MouseWheelEvent) => { const { deltaY } = e const newScale: number = deltaY > 0 ? (canvasScale * 10 - 0.1 * 10) / 10 : (canvasScale * 10 + 0.1 * 10) / 10 if (newScale < 0.1 || newScale > 2) return setCanvasScale(newScale) } } const handleScaleChange = (value: number) => { setCanvasScale(value) } const handleLineWidthChange = (value: number) => { setLineWidth(value) } const handleColorChange = (color: string) => { setLineColor(color) } const handleMouseModeChange = (event: RadioChangeEvent) => { const { target: { value } } = event const { current: canvas } = canvasRef const { current: wrap } = wrapRef setmouseMode(value) if (!canvas || !wrap) return switch (value) { case MOVE_MODE: canvas.style.cursor = 'move' wrap.style.cursor = 'move' break case LINE_MODE: canvas.style.cursor = `url('http://cdn.algbb.cn/pencil.ico') 6 26, pointer` wrap.style.cursor = 'default' break case ERASER_MODE: message.warning('橡皮擦作用并未健全,储存照片会出現不正确') canvas.style.cursor = `url('http://cdn.algbb.cn/eraser.ico') 6 26, pointer` wrap.style.cursor = 'default' break default: canvas.style.cursor = 'default' wrap.style.cursor = 'default' break } } const handleSaveClick = () => { const { current: canvas } = canvasRef // 可存入数据信息库或是立即转化成照片 console.log(canvas?.toDataURL()) } const handlePaperChange = (value: string) => { const fillImageList = { 'xueshengjia': 'http://cdn.algbb.cn/test/canvasTest.jpg', 'xueshengyi': 'http://cdn.algbb.cn/test/canvasTest2.png', 'xueshengbing': 'http://cdn.algbb.cn/emoji/30.png', } setFillImageSrc(fillImageList[value]) } const handleRollBack = () => { const isFirstHistory: boolean = canvasCurrentHistory === 1 if (isFirstHistory) return setCanvasCurrentHistory(canvasCurrentHistory - 1) } const handleRollForward = () => { const { current: canvasHistroyList } = canvasHistroyListRef const isLastHistory: boolean = canvasCurrentHistory === canvasHistroyList.length if (isLastHistory) return setCanvasCurrentHistory(canvasCurrentHistory + 1) } const handleClearCanvasClick = () => { const { current: canvas } = canvasRef const context: CanvasRenderingContext2D | undefined | null = canvas?.getContext('2d') if (!canvas || !context || canvasCurrentHistory === 0) return // 清空画布历史时间 canvasHistroyListRef.current = [canvasHistroyListRef.current[0]] setCanvasCurrentHistory(1) message.success('画布消除取得成功!') } return ( <div> <CustomBreadcrumb list={['內容管理方法', '审阅工作']} /> <div className="mark-paper__container" ref={containerRef}> <div className="mark-paper__wrap" ref={wrapRef}> <div className="mark-paper__mask" style={{ display: isLoading ? 'flex' : 'none' }} > <Spin tip="照片载入中..." indicator={<Icon type="loading" style={{ fontSize: 36 }} spin />} /> </div> <canvas ref={canvasRef} className="mark-paper__canvas"> <p>很可是,这个东东与您的电脑上不配!</p> </canvas> </div> <div className="mark-paper__sider"> <div> 挑选工作: <Select defaultValue="xueshengjia" style={{ width: '100%', margin: '10px 0 20px 0' }} onChange={handlePaperChange} > <OptGroup label="17手机软件1班"> <Option value="xueshengjia">学员甲</Option> <Option value="xueshengyi">学员乙</Option> </OptGroup> <OptGroup label="17手机软件2班"> <Option value="xueshengbing">学员丙</Option> </OptGroup> </Select> </div> <div> 画布实际操作:<br /> <div className="mark-paper__action"> <Tooltip title="撤消"> <i className={`icon iconfont icon-chexiao ${canvasCurrentHistory <= 1 && 'disable'}`} onClick={handleRollBack} /> </Tooltip> <Tooltip title="修复"> <i className={`icon iconfont icon-fanhui ${canvasCurrentHistory >= canvasHistroyListRef.current.length && 'disable'}`} onClick={handleRollForward} /> </Tooltip> <Popconfirm title="明确清空画布吗?" onConfirm={handleClearCanvasClick} okText="明确" cancelText="撤销" > <Tooltip title="清空"> <i className="icon iconfont icon-qingchu" /> </Tooltip> </Popconfirm> </div> </div> <div> 画布放缩: <Tooltip placement="top" title='能用电脑鼠标滚轮开展放缩'> <Icon type="question-circle" /> </Tooltip> <Slider min={0.1} max={2.01} step={0.1} value={canvasScale} tipFormatter={(value) => `${(value).toFixed(2)}x`} onChange={handleScaleChange} /> </div> <div> 画笔尺寸: <Slider min={1} max={9} value={lineWidth} tipFormatter={(value) => `${value}px`} onChange={handleLineWidthChange} /> </div> <div> 方式挑选: <Radio.Group className="radio-group" onChange={handleMouseModeChange} value={mouseMode}> <Radio value={0}>挪动</Radio> <Radio value={1}>画笔</Radio> <Radio value={2}>橡皮擦</Radio> </Radio.Group> </div> <div> 色调挑选: <div className="color-picker__container"> {['#fa4b2a', '#ffff00', '#ee00ee', '#1890ff', '#333333', '#ffffff'].map(color => { return ( <Tooltip placement="top" title={color} key={color}> <div role="button" className={`color-picker__wrap ${color === lineColor && 'color-picker__wrap--active'}`} style={{ background: color }} onClick={() => handleColorChange(color)} /> </Tooltip> ) })} </div> </div> <Button onClick={handleSaveClick}>储存照片</Button> </div> </div> </div > ) } export default MarkPaper as ComponentType
总结
到此这篇有关Html5 Canvas完成照片标识、放缩、挪动和储存历史时间情况 (附变换公式)的文章内容就详细介绍到这了,更多有关Canvas 照片标识 放缩 挪动內容请检索脚本制作之家之前的文章内容或再次访问下面的有关文章内容,期待大伙儿之后多多适用脚本制作之家!
Copyright © 2002-2020 ps网页版在线制作_静态网页制作方法_微云网页版_怎么制作一个网页_django网页模板 版权所有 (网站地图) 粤ICP备10235580号