当前位置: 首页 > news >正文

网站建设基础条件成都广告公司有哪些

网站建设基础条件,成都广告公司有哪些,seo优化技术培训,python在线编程器Canvas简历编辑器-选中绘制与拖拽多选交互设计 在之前我们聊了聊如何基于Canvas与基本事件组合实现了轻量级DOM#xff0c;并且在此基础上实现了如何进行管理事件以及多层级渲染的能力设计。那么此时我们就依然在轻量级DOM的基础上#xff0c;关注于实现选中绘制与拖拽多选交…Canvas简历编辑器-选中绘制与拖拽多选交互设计 在之前我们聊了聊如何基于Canvas与基本事件组合实现了轻量级DOM并且在此基础上实现了如何进行管理事件以及多层级渲染的能力设计。那么此时我们就依然在轻量级DOM的基础上关注于实现选中绘制与拖拽多选交互设计。 在线编辑: https://windrunnermax.github.io/CanvasEditor开源地址: https://github.com/WindrunnerMax/CanvasEditor 关于Canvas简历编辑器项目的相关文章: 掘金老给我推Canvas我也学习Canvas做了个简历编辑器Canvas图形编辑器-数据结构与History(undo/redo)Canvas图形编辑器-我的剪贴板里究竟有什么数据Canvas简历编辑器-图形绘制与状态管理(轻量级DOM)Canvas简历编辑器-MonorepoRspack工程实践Canvas简历编辑器-层级渲染与事件管理能力设计Canvas简历编辑器-选中绘制与拖拽多选交互方案 选中绘制 我们先来聊一聊最基本的节点点击选中以及拖拽的交互而在聊具体的代码实现之前我们先来看一下对于图形的绘制问题。在Canvas中我们绘制路径的话我们可以通过fill来填充路径也可以通过stroke来描边路径而在我们描边的时候如果不注意的话可能会陷入一些绘制的问题。假如此时我们要绘制一条线我们可以分别来看下使用stroke和fill的绘制方法实现此时如果在高清ctx.scale(devicePixel, devicePixel)情况下则能明显地看出来绘制位置差0.5px而如果基准为1px的话则会出现1px的差值以及色值偏差。 ctx.beginPath(); ctx.strokeStyle blue; ctx.lineWidth 1; ctx.moveTo(5, 5); ctx.lineTo(100, 5); ctx.closePath(); ctx.stroke(); ctx.fillStyle red; ctx.beginPath(); ctx.moveTo(100, 5); ctx.lineTo(200, 5); ctx.lineTo(200, 6); ctx.lineTo(100, 6); ctx.closePath(); ctx.fill();在先前的选中图形frame中我们都是用stroke来实现的然后最近我想将其真正作为外边框来绘制然后就发现想绘制inside stroke确实不是一件容易的事。从MDN上阅读stroke的文档可以得到其是以路径的中心线为基准的也就是说stroke是由基准分别向内外扩展的那么问题就来了假如我们绘制了一条线而这条线本身是存在1px宽度的那么初步理解按照文档所说其本身结构应该是以这1px本身的中心点也就是0.5px的位置为中心点向外发散然而其实际效果是以1px的外边缘为基准发散那么就会导致1px的线在stroke之后会多出0.5px的宽度这个效果可以通过lineTo(0, 100)外加lineWith1来测试可以发现其可见宽度只有0.5px这点可以通过再画一个1px的Path来对比。 ctx.beginPath(); ctx.lineWidth 6; ctx.strokeStyle blue; ctx.moveTo(0, 0); ctx.lineTo(100, 0); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle red; ctx.moveTo(100, 3); ctx.lineTo(200, 3); ctx.closePath(); ctx.stroke();那么这里的Strokes are aligned to the center of a path可能与我理解的center of a path并不相同或许其只是想表达stroke是分别向两侧绘制描边的而并不是解释其基准位置。关于这个问题我咨询了一下这里主要是理解有偏差在我们使用API绘制路径时本身并没有设置宽度的信息而坐标信息定义的是路径的轮廓或边界因此我们在最开始定义的路径结构1px是不成立的。在图形学的上下文中路径path通常是指一个几何形状的轮廓或线条路径本身是数学上的抽象概念没有宽度只是一个由点和线段构成的轨迹因此当我们提到描边stroke时指的是一个可视化过程即在路径的周围绘制有宽度的线条。 实际上这里如果仅仅是处理frame的问题的话可能并没有太大的问题然而在处理节点的时候发现由于是使用stroke绘制的操作节点那么实际上其总是会超出原始宽度的也就是上边说的描边问题而因为超出的这0.5px的边缘节点使得我一直认为绘制节点的边缘与填充是没问题的然而今天才发现这里的顺序反了描边的内部会被填充覆盖掉也就是说实现的border宽度总是会被除以2的因此要先填充再描边才是正确的绘制方式。此外无论是frame节点的绘制还是类似border的绘制在Firefox中inside stroke总是会出现兼容性问题仅有组合fill以及使用fill配合Path2D clip才能绘制正常的inside stroke。 ctx.save(); ctx.beginPath(); ctx.arc(70, 75, 50, 0, 2 * Math.PI); ctx.stroke(); ctx.fillStyle white; ctx.fill(); ctx.closePath(); ctx.restore();ctx.save(); ctx.beginPath(); ctx.arc(200, 75, 50, 0, 2 * Math.PI); ctx.fillStyle white; ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.restore();那么我们就可以利用三种方式绘制inside stroke当然还有借助lineTo/fillRect分别绘制4条边的方式我们没有列举因为这种方式自然不会出现什么问题其本身就是使用fill的方式绘制的而我们这里主要是讨论stroke的绘制问题只是借助Path2D同样也是fill的方式绘制的但是这里需要讨论一下clip的fillRule-nonzero/evenodd的问题。那么借助stroke的特性方式1是我们绘制两倍的lineWidth然后裁剪掉外部的描边部分这样就能够正确保留内部的描边了方式2则是我们主动校准了描边的位置将其向内缩小0.5px的位置由此来绘制完整的描边方式3是借助evenodd的填充规则通过clip来生成规则保留内部的描边再来实际填充即可实现。 canvas idcanvas width800 height800/canvas script// https://stackoverflow.com/questions/36615592/canvas-inner-strokeconst canvas document.getElementById(canvas);const ctx canvas.getContext(2d);const devicePixelRatio Math.ceil(window.devicePixelRatio || 1);const width canvas.clientWidth;const height canvas.clientHeight;canvas.width width * devicePixelRatio;canvas.height height * devicePixelRatio;canvas.style.width width px;canvas.style.height height px;ctx.scale(devicePixelRatio, devicePixelRatio);ctx.save();ctx.beginPath();ctx.rect(10, 10, 150, 100);ctx.clip();ctx.closePath();ctx.lineWidth 2;ctx.strokeStyle blue;ctx.stroke();ctx.restore();ctx.save();ctx.beginPath();ctx.rect(170 0.5, 10 0.5, 150 - 1, 100 - 1);ctx.closePath();ctx.lineWidth 1;ctx.strokeStyle blue;ctx.stroke();ctx.restore();ctx.save();ctx.beginPath();const region new Path2D();region.rect(330, 10, 150, 100);region.rect(330 1, 10 1, 150 - 2, 100 - 2);ctx.clip(region, evenodd);ctx.rect(330, 10, 150, 100);ctx.closePath();ctx.fillStyle blue;ctx.fill();ctx.restore(); /script那么先前我们也提到了在Firefox浏览器的兼容性问题那么我们将上述的实现方式在Firefox中进行测试可以发现inside stroke的绘制是有些许问题的第一个图形明显左上的线比右下的线细一些第二个图形则明显会粗糙一些第三个图形则看起来绘制更细致更符合1px的绘制。因此我们如果想要兼容绘制inside stroke的话最好的方式还是选择方式三当然像最开始的实现中借助lineTo/fillRect分别绘制4条边的方式自然也是没问题的两者的性能对比在后边也可以尝试实验一下。 那么接着我们就回到在轻量级DOM上实现选中的绘制首先我们对基本节点的事件做一些通用的实现我们先来实现点击的选取。因为在之前我们已经定义好了事件的基本传递那么我们此时只需要在Element节点上实现事件的响应即可那么在这里我们就可以直接操作选区模块直接将当前的活跃节点id设置为节点组的内容即可。 // packages/core/src/canvas/dom/element.ts export class ElementNode extends Node {protected onMouseDown (e: MouseEvent) {this.editor.selection.setActiveDelta(this.id);}; }而当我们触发选区的节点设置之后在选区模块则会将此时所有的active节点组合起来形成新的Range然后在新的Range基础上判断当前是否应该触发选区变换的事件这里的事件分发比较重要整个编辑器的选区变化事件都会在此处分发。 // packages/core/src/selection/index.ts export class Selection {public set(range: Range | null) {if (this.editor.state.get(EDITOR_STATE.READONLY)) return this;const previous this.current;if (Range.isEqual(previous, range)) return this;this.current range;this.editor.event.trigger(EDITOR_EVENT.SELECTION_CHANGE, {previous,current: range,});return this;}public setActiveDelta(...deltaIds: string[]) {this.active.clear();deltaIds.forEach(id this.active.add(id));this.compose();}public compose() {const active this.active;if (active.size 0) {this.set(null);return void 0;}let range: Range | null null;active.forEach(key {const delta this.editor.deltaSet.get(key);if (!delta) return void 0;const deltaRange Range.from(delta);range range ? range.compose(deltaRange) : deltaRange;});this.set(range);} }那么在事件分发之后我们必须要在选区变换之后绘制新的选区实际上在选区变换后我们理论上仅仅需要将节点绘制出来即可而按照我们先前的调度设计而言我们需要主动按需触发要绘制的区域并且由于选区是由其他的位置变换到当前区域的因此绘制时就需要将先前的区域同时绘制。那么按照我们先前的设计SelectNode本身既是事件处理器又是渲染器基本与DOM节点基本一致只是我们绑定事件和绘制都是直接由类控制而已而在drawingMask的Shape.frame绘制中就是我们最开始聊的描边与填充绘制问题。 // packages/core/src/canvas/dom/node.ts export class SelectNode extends Node {protected onSelectionChange (e: SelectionChangeEvent) {const { current, previous } e;this.editor.logger.info(Selection Change, current);const range current || previous;if (range) {const refresh range.compose(previous).compose(current);this.editor.canvas.mask.drawingEffect(refresh.zoom(RESIZE_OFS));}};public drawingMask (ctx: CanvasRenderingContext2D) {const selection this.editor.selection.get();if (selection) {const { x, y, width, height } selection.rect();Shape.frame(ctx, { x, y, width, height, borderColor: BLUE_6 });}}; }拖拽多选 当我们已经成功实现图形单选以及节点绘制之后我们很容易想到两个交互问题首先是图形的多选因为我们在选中节点的时候可能不会仅仅选一个节点例如全选的场景其次则是选中图形的拖拽这个就是常见的交互方式了无论是单选还是多选的时候都可以通过拖拽图形来调整位置。那么我们首先来看一下多选实际上在上边我们的设计中本就是支持多选的我们在选区的active就是Setstring类型以及Selection的compose方法也是支持多选的那么我们只需要在选中节点的时候将节点的id添加到active中即可。 // packages/core/src/canvas/dom/element.ts export class ElementNode extends Node {protected onMouseDown (e: MouseEvent) {if (e.shiftKey) {this.editor.selection.addActiveDelta(this.id);} else {this.editor.selection.setActiveDelta(this.id);}}; }除了按住shiftKey键进行多选之外我们使用鼠标以某个点为起点拖拽选区进行选择也是一种多选的方式那么在这里我们将这个交互方式设计在了FrameNode内而这里有点不同的是我们的起始行为需要归并到Root节点上因为只有点击在Root节点上的事件我们才认为是起始否则是认为点击到了节点本身上而框选这个交互的本身事件则主要是判断当前的选区大小以及其覆盖的节点范围将覆盖的节点id全部放置于选区模块即可。 // packages/core/src/canvas/dom/frame.ts export class FrameNode extends Node {private onRootMouseDown (e: MouseEvent) {this.savedRootMouseDown(e);this.unbindOpEvents();this.bindOpEvents();this.landing Point.from(e.x, e.y);this.landingClient Point.from(e.clientX, e.clientY);};private onMouseMoveBridge (e: globalThis.MouseEvent) {if (!this.landing || !this.landingClient) return void 0;const point Point.from(e.clientX, e.clientY);const { x, y } this.landingClient.diff(point);if (!this.isDragging (Math.abs(x) SELECT_BIAS || Math.abs(y) SELECT_BIAS)) {// 拖拽阈值this.isDragging true;}if (this.isDragging) {const latest new Range({startX: this.landing.x,startY: this.landing.y,endX: this.landing.x x,endY: this.landing.y y,}).normalize();this.setRange(latest);// 获取获取与选区交叉的所有State节点const effects: string[] [];this.editor.state.getDeltasMap().forEach(state {if (latest.intersect(state.toRange())) effects.push(state.id);});this.editor.selection.setActiveDelta(...effects);// 重绘拖拽过的最大区域const zoomed latest.zoom(RESIZE_OFS);this.dragged this.dragged ? this.dragged.compose(zoomed) : zoomed;this.editor.canvas.mask.drawingEffect(this.dragged);}};private onMouseMoveController throttle(this.onMouseMoveBridge, ...THE_CONFIG);private onMouseUpController () {this.unbindOpEvents();this.setRange(Range.reset());if (this.isDragging) {this.dragged this.editor.canvas.mask.drawingEffect(this.dragged);}this.landing null;this.isDragging false;this.dragged null;this.setRange(Range.reset());};public drawingMask (ctx: CanvasRenderingContext2D) {if (this.isDragging) {const { x, y, width, height } this.range.rect();Shape.rect(ctx, { x, y, width, height, borderColor: BLUE_5, fillColor: BLUE_6_6 });}}; }说到这里在多选之外这里我们可能还需要关注一个交互就是Hover的效果。如果我们是CSS实现的话这个问题实际上很简单无非是增加一个伪类的问题然而在Canvas中我们需要自己实现这个效果也就是需要借助MouseEvent来手动处理这个过程。当然思路是比较简单的我们只需要维护一个boolean的id标识来确定当前节点是否被Hover然后根据选区状态来判断是否需要绘制当前节点的Range即可。 // packages/core/src/canvas/dom/element.ts export class ElementNode extends Node {protected onMouseEnter () {this.isHovering true;if (this.editor.selection.has(this.id)) {return void 0;}this.editor.canvas.mask.drawingEffect(this.range);};protected onMouseLeave () {this.isHovering false;if (this.editor.selection.has(this.id)) {return void 0;}this.editor.canvas.mask.drawingEffect(this.range);};public drawingMask (ctx: CanvasRenderingContext2D) {if (this.isHovering !this.editor.selection.has(this.id) !this.editor.state.get(EDITOR_STATE.MOUSE_DOWN)) {const { x, y, width, height } this.range.rect();Shape.frame(ctx, {x: x,y: y,width: width,height: height,borderColor: BLUE_4,});}}; }而事件的调度则是由Root节点来实现的这里主要是维护了一个互斥的hoverId来实现的当然这里的主要目的还是模拟OnMouseEnter以及OnMouseLeave事件。基本逻辑是遍历当前的节点如果发现需要触发相关事件的节点则判断鼠标是否在当前节点内如果在节点内则作为命中的节点判断当前Hover的节点如果与先前不一致则根据具体的条件来判断并且触发先前的节点MouseLeave与当前节点MouseEnter事件。 // packages/core/src/canvas/state/root.ts export class Root extends Node {/** Hover 节点 */public hover: ElementNode | ResizeNode | null;private onMouseMoveBasic (e: globalThis.MouseEvent) {// 非默认状态下不执行事件if (!this.engine.isDefaultMode()) return void 0;// 按事件顺序获取节点const flatNode this.getFlatNode();let next: ElementNode | ResizeNode | null null;const point Point.from(e, this.editor);for (const node of flatNode) {// 当前只有ElementNode和ResizeNode需要触发Mouse Enter/Leave事件const authorize node instanceof ElementNode || node instanceof ResizeNode;if (authorize node.range.include(point)) {next node;break;}}// 如果命中的节点与先前 Hover 的节点不一致if (this.hover ! next) {const prev this.hover;this.hover next;if (prev ! null) {this.emit(prev, NODE_EVENT.MOUSE_LEAVE, MouseEvent.from(e, this.editor));if (prev instanceof ElementNode) {this.editor.event.trigger(EDITOR_EVENT.HOVER_LEAVE, { node: prev });}}if (next ! null) {this.emit(next, NODE_EVENT.MOUSE_ENTER, MouseEvent.from(e, this.editor));if (next instanceof ElementNode) {this.editor.event.trigger(EDITOR_EVENT.HOVER_ENTER, { node: next });}}}}; }紧接着我们就来聊一聊选区节点的拖拽移动问题关于这部分能力的实现我们将其作为了SelectNode的一部分实现。对于拖拽这件事本身来说我们只需要关注MouseDown绑定事件、MouseMove移动、MouseUp取消绑定事件那么这里我们同样也是类似的实现只不过由于我们需要考虑节点的绘制因此需要在其中穿插着图形的drawing方法调用。在这里我们采用了最方便的按需绘制方案即所有拖拽过的区域都重新绘制当然最好的方案还是当前事件触发区域的重绘这样性能会更好一些且在这里我们只绘制拖拽的边框而不是将所有节点都拖拽着绘制。此外在这里我们还实现了交互上的优化即只有拖拽超过一定的阈值才会触发拖拽事件这样可以避免误操作。 // packages/core/src/canvas/dom/select.ts export class SelectNode extends Node {private onMouseDownController (e: globalThis.MouseEvent) {// 非默认状态下不执行事件if (!this.editor.canvas.isDefaultMode()) return void 0;// 取消已有事件绑定this.unbindDragEvents();const selection this.editor.selection.get();// 选区 严格点击区域判定if (!selection || !this.isInSelectRange(Point.from(e, this.editor), this.range)) {return void 0;}this.dragged selection;this.landing Point.from(e.clientX, e.clientY);this.bindDragEvents();this.refer.onMouseDownController();};private onMouseMoveBasic (e: globalThis.MouseEvent) {const selection this.editor.selection.get();if (!this.landing || !selection) return void 0;const point Point.from(e.clientX, e.clientY);const { x, y } this.landing.diff(point);// 超过阈值才认为正在触发拖拽if (!this._isDragging (Math.abs(x) SELECT_BIAS || Math.abs(y) SELECT_BIAS)) {this._isDragging true;}if (this._isDragging selection) {const latest selection.move(x, y);const zoomed latest.zoom(RESIZE_OFS);// 重绘拖拽过的最大区域this.dragged this.dragged ? this.dragged.compose(zoomed) : zoomed;this.editor.canvas.mask.drawingEffect(this.dragged);const offset this.refer.onMouseMoveController(latest);this.setRange(offset ? latest.move(offset.x, offset.y) : latest);}};private onMouseMoveController throttle(this.onMouseMoveBasic, ...THE_CONFIG);private onMouseUpController () {this.unbindDragEvents();this.refer.onMouseUpController();const selection this.editor.selection.get();if (this._isDragging selection) {const rect this.range;const { startX, startY } selection.flat();const ids [...this.editor.selection.getActiveDeltaIds()];this.editor.state.apply(new Op(OP_TYPE.MOVE, { ids, x: rect.start.x - startX, y: rect.start.y - startY }));this.editor.selection.set(rect);this.dragged this.editor.canvas.mask.drawingEffect(this.dragged);}this.landing null;this.dragged null;this._isDragging false;}; }最后 在这里我们就依然在轻量级DOM的基础上讨论了Canvas中描边与填充的绘制问题以及inside stroke的实现方式然后我们实现了基本的选中绘制以及拖拽多选的交互设计并且实现了Hover的效果以及拖拽节点的移动。那么在后边我们可以聊一下fillRule规则设计、按需绘制图形节点也可以聊到更多的交互设计例如Resize的交互设计、参考线能力的实现、富文本的绘制方案等等。
http://www.dnsts.com.cn/news/18844.html

相关文章:

  • 怎么更改织梦网站文章样式富民网站建设
  • 织梦网站模板怎么做瑶海合肥网站建设
  • 做网站编程河北新河网
  • 东莞建站模板搭建wordpress 跳转到首页
  • asp网站伪静态医院网站建设山东
  • 做微信公众平台的网站wordpress 大赛 投票
  • 网站制作的网站开发asp.net构建门户网站
  • 站内站怎么搭建住房和城乡建设厅焊工证
  • 网站开发价格预算wordpress 订单插件
  • 包装设计网站哪个好用注册网站那里能注册
  • 门户网站开发费用济南代做标书网站标志
  • 北京网站建设学校wordpress 画面做成
  • 广州网站下载安装wordpress搬家建立数据库连接时出错
  • 云南网站备案系统dede网站5.7广告去除
  • 网站改标题降权图片展示模块网站做一个多少钱
  • 如何在服务器上关闭网站wordpress移动版设置
  • 崇左市城市投资建设有限公司网站公司小程序开发哪家好
  • 多语言网站如何开发简单网页制作图片
  • 企石镇网站建设湘潭网站网站建设
  • 网站建设中忽略的字体侵权行为网站建设培训机构
  • 建设营销网站多少钱动漫设计培训学校
  • 注册网站的步骤青岛网站关键词
  • 如何创建网站的步骤品牌网站建设k小蝌蚪
  • asp手机网站模板wordpress编辑模板下载
  • 大连网站建设服务公司青岛网站建设邓巴迪
  • 模板建站wordpress淘宝客排名主题
  • 长沙大型网站建设公司资阳住房和城乡建设厅官方网站
  • 阿勒泰网站建设南昌制作网站软件
  • 做网站优化的协议书制作图
  • 如何建立一个免费网站中企动力官网登陆