1 . 创建一个简易的画布
.canvas{
width: '100vw';
height: '100vh'
}
<canvas className='canvas' id="drawCanvas" ></canvas>
2 . 监听canvas上的移动事件。我们可以判断下当前设备是否支持PointerEvent事件,因为Pointer Events 事件将鼠标(Mouse)、触摸(touch) 和触控笔(pen)三种事件整合为统一的 API。不支持的话我们用mouseEvent事件监听,mouseButtonDown变量记录下当前用户是否在绘图,mousedown按下画笔开始绘图,mouseup鼠标放开停止绘图。在绘制的过程中我们需要记录下当前鼠标的位置。
function handleMouseDown(event: any) {
mouseButtonDown = true;
}
function handleMouseMove(event) {
if (mouseButtonDown) {
lastPt = {
x: event.pageX,
y: event.pageY
}
}
}
function handleMouseUp(event: any) {
mouseButtonDown = false;
lastPt = {x: null, y: null};
}
useEffect(() => {
let canvas: any = document.getElementById('drawCanvas');
if (!canvas) {
return
}
mouseButtonDown = false;
if (window.PointerEvent) {
canvas.addEventListener('pointerdown', handleMouseDown, false);
canvas.addEventListener('pointermove', handleMouseMove, false);
canvas.addEventListener('pointerup', handleMouseUp, false);
} else {
canvas.addEventListener('mousedown', handleMouseDown, false);
canvas.addEventListener('mousemove', handleMouseMove, false);
canvas.addEventListener('mouseup', handleMouseUp, false);
}
}, [])
3 . 增加绘图函数
function draw(pathInfo, useCtx) {
if (pathInfo.beginX !== null && pathInfo.beginY !== null) {
const {lastX, lastY, beginX, beginY, strokeStyle, lineWidth, drawType} = pathInfo;
useCtx.beginPath(); // 开始绘图
useCtx.lineCap = 'round'; // 绘制线的两头是圆形,其他形状也可以,不加的话,在线条宽度很大的情况下,画出来的线条横向一条一条的
useCtx.moveTo(beginX, beginY); // 将鼠标移动到beginX, beginY
useCtx.lineTo(lastX, lastY); // 指定lastX, lastY为终点(lineTo不会绘制路径)
useCtx.strokeStyle = strokeStyle || 'green'; // 设置线的颜色
useCtx.lineWidth = lineWidth || 3; // 设置线的宽度
useCtx.stroke(); // 绘制路径
useCtx.closePath(); // 结束绘制,不结束的话,无法改变后面新增路径的颜色大小等数据
}
}
一个超简易的画板就实现了,我们滑动鼠标看下效果。
咦,发现鼠标滑过的位置和线条真正绘制的位置不一样,并且绘制的线条也比较模糊。经查阅,这是因为两个问题:1. 没有处理dpr,2. canvas的宽高设置不正确。
位置不正确是因为 canvas.width是画布的大小,决定了多少像素可以显示在画布上。canvas.style,width是浏览器渲染的canvas尺寸,也就是屏幕上元素显示的大小。当只设置css宽高时,canvas画布会使用默认的宽300px,高150px,然后根据设置的css宽高进行一定比例转化,造成相应的拉伸变形,展示的位置不正确。所以我们画布的宽高在canvas标签中设置,或者在js中动态设置就能解决位置偏移问题。另外,规范中描述画布的宽高在指定的时候必须是非负整数,如果带上百分号或者其他单位,会解析错误,直接使用默认数值。
像素不清晰是因为 canvas 绘制时独立于设备像素比(devicePixelRatio)。受到 devicePixelRatio 影响,在高清显示屏上,一个逻辑像素对应多个实际的设备物理像素。例如在 devicePixelRatio 为 2 的设备上,css 设置的 100px,意味着设备上要填充 200px 物理像素。解决思路就是将canvas的style中的width和height值设置为要显示的大小,然后将canvas的width和height的值,根据dpi的倍数进行放大。这时,画布中所有线条文字等都需要等比放大,用scale方法将画布中所有内容放大。
let dpr = window.devicePixelRatio || 1;
let canvas: any = document.getElementById('drawCanvas');
// 设置实际尺寸
canvas.width = (document.body.clientWidth - 20) * dpr;
canvas.height = (document.body.clientHeight - 20) * dpr;
canvas.style.width = (document.body.clientWidth - 20) + 'px';
canvas.style.height = (document.body.clientHeight - 20) + 'px'
// 让canvas坐标系统使用css像素
let ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
现在看起来效果可以了,我们给画布加一个一键清空的操作。
4 . 简易清空操作
canvas有三种清空方式。
(1) 简单填充 使用一个新的背景色简单地填充整个画布,这样就可以清除当前内容
ctx.fillStyle = '#fff';
let rect = this.canvas.getBoundingClientRect();
ctx.fillRect(rect.x, rect.y, rect.width, rect.height)
(2)重置画布高度 当画布的宽或高被重置时,当前画布内容就会被移除。
let rect = this.canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
(3)使用clearRect函数 clearRect() 函数可以指定起始点的x, y 位置以及宽度和高度来清除画布
let rect = this.canvas.getBoundingClientRect();
this.ctx.clearRect(rect.x, rect.y, rect.width, rect.height);
好了,到目前我们实现了一个画板,能在上面绘制清空,现在来为这个画板加一些其他操作吧。
1 . 改变线条颜色
这里我引入了 react-color 库,一种颜色选择器组件。
<CirclePicker color={config.strokeStyle} onChange={(color: any) => config.strokeStyle = color.hex} />
监听更改颜色的方案有很多种,这里我选择了用proxy监听数据。
new Proxy(target, {
// target 目标对象 property 更改的属性名 value 新属性值 receiver 最初被调用的对象。通常是 proxy 本身
set(target, property, value, receiver) {
// Reflect.set 在对象上设置一个属性,返回boolean
const result = Reflect.set(target, property, value, receiver);
return result;
},
})
2 . 改变线条大小
<p>当前笔刷宽度为:{config.lineWidth}</p>
<input
type="range"
min={1}
max={100}
value={config.lineWidth}
onChange={e => (config.lineWidth = Number(e.target.value))}
/>
3 . 增加橡皮擦
将线条颜色换成白色,再加一个能选择透明度大小和一个橡皮擦宽度大小的input框,一个简单的橡皮擦就实现了。
透明度:<input
type="range"
min={1}
max={10}
defaultValue={10}
onChange={e => (config.strokeStyle = `rgba(255, 255, 255, ${Number(e.target.value) / 10})`)}
/><br />
橡皮擦大小:<input
type="range"
min={1}
max={100}
value={config.lineWidth}
onChange={e => (config.lineWidth = Number(e.target.value))}
/>
三、撤销操作
canvas 有原生的restore/save两个api,save时将当前状态放入栈中,restore从canvas保存的绘图状态栈中弹出顶端的状态。但这个api只保存部分状态,drawImage 这种操作对画布的改变是不会被canvas记录的,所以我们需要自己模拟一个栈,绘图的时候将最新的线条数据插入,撤销的时候将最上面的线条数据扔掉,重新绘制。
思路是声明保存当前所有画布线条信息的一个数组。数组里的每一条对象也是一个数组,记录了当前的绘图曲线。当鼠标按下,以及移动过程中将坐标,线条大小及颜色等数据放入数组中,直到鼠标放开。不然只有鼠标按下和放开两个节点坐标,canvas在绘制时直接生成了一条直线,而不是我们实际绘图弯弯绕绕的曲线。点击撤销的时候,我们把栈中最后一条数据扔掉,然后循环数组中的每条数据,进行重新绘制。
// 当前画布的所有绘制线条信息
let pathData = new Proxy(pathData, {get: () => {...}, set: () => {...}})
// 单独的线条数据
let singlePathData:any[] = [];
function handleMouseDown(event: any) {
mouseButtonDown = true;
}
function handleMouseMove(event) {
if (mouseButtonDown) {
let singleData = {beginX: lastPt.x, beginY: lastPt.y, lastX: event.pageX, lastY: event.pageY, strokeStyle: config.strokeStyle, lineWidth: config.lineWidth, drawType: config.drawType};
singlePathData.push(singleData)
draw(singleData)
lastPt = {
x: event.pageX,
y: event.pageY
}
}
}
function handleMouseUp(event: any) {
mouseButtonDown = false;
lastPt = {x: null, y: null};
pathData.push(singlePathData)
singlePathData = [];
console.log(pathData, 'pathData')
}
// 撤销函数
function undo() {
pathData.pop();
let canvasDom: any = document.getElementById('drawCanvas');
let curCtx = canvasDom!.getContext('2d');
let rect = canvasDom!.getBoundingClientRect();
curCtx.clearRect(rect.x, rect.y, rect.width, rect.height);
pathData.map(item => {
item.map(info => draw(info, curCtx))
})
}
这样的数据结构我们不用担心需要对橡皮擦或者以后其他增加的画板操作进行额外的处理,毕竟本质都是一条可绘制出的完整线条数据,当pathData为空时,说明当前画板被清空了,点击撤销直接return。
点击完成按钮将当前canvas画板上的内容生成一张png图片。这块直接用原生api canvas.toDataURL("image/png")
就可以实现。
调取showSaveFilePicker 方法可以将生成的图片保存到指定的目录。该方法返回一个FileSystemFileHandle 对象,这个对象上的方法可以操作文件。
const onSave = () => {
const handle = await (window as any).showSaveFilePicker({
suggestedName: "test.png",
types: [
{
description: "PNG file",
accept: {
"image/png": [".png"],
},
},
],
});
const writable = await handle.createWritable();
await writable.write(props.imgUrl);
await writable.close();
return handle;
}
为什么Proxy一定要配合Reflect使用?
- END -
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Z51xuzSL2wnnJMJnX1NtbQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。