Canvas简历编辑器-选中绘制与拖拽多选交互设计
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简历编辑器-Monorepo+Rspack工程实践
- 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)
外加lineWith=1
来测试,可以发现其可见宽度只有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 id="canvas" width="800" height="800"></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
就是Set<string>
类型,以及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
的交互设计、参考线能力的实现、富文本的绘制方案等等。
相关文章:
Canvas简历编辑器-选中绘制与拖拽多选交互设计
Canvas简历编辑器-选中绘制与拖拽多选交互设计 在之前我们聊了聊如何基于Canvas与基本事件组合实现了轻量级DOM,并且在此基础上实现了如何进行管理事件以及多层级渲染的能力设计。那么此时我们就依然在轻量级DOM的基础上,关注于实现选中绘制与拖拽多选交…...
简单工厂(Simple Factory)
简单工厂(Simple Factory) 在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。 说明: 简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪…...
ffmpeg拉流分段存储到文件-笔记
通过ffmpeg可以从rtsp网络流拉取数据并存储到本地文件里,如下命令。做个笔记 ffmpeg -rtsp_transport tcp -i rtsp://192.168.1.168:6880/live -c copy -f segment -segment_time 60 stream_piece_%d.mp4这条 ffmpeg 命令的作用是从一个 RTSP 流中捕获视频ÿ…...
Java 实习工资大概是多少?——解读影响薪资的因素
文章目录 1. 城市因素:一线、二线的差距2. 公司类型:互联网公司、外企和传统企业的差别3. 个人能力:经验、技术栈的重要性4. 其他影响因素:学历和实习时间总结推荐阅读文章 Java 开发作为广泛应用的职业方向,实习工资的…...
【Linux】万字详解:Linux文件系统与软硬链接
🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 🚀 前言 一: 🔥 磁盘的物理结构二: 🔥 磁盘的存储结构 三: 🔥 磁盘的逻辑结构 四: &#…...
spacenavd
介绍spacenavd开源项目,主要是因为在斯坦福大学的UMI项目中使用了该项目。在斯坦福大学的 UMI(Universal Manipulation Interface)项目中,Spacenavd 主要用于处理 3D Space Mouse(空间鼠标)的输入…...
C#WPF的XAML的语法详谈和特性
WPF的XAML(eXtensible Application Markup Language)是一种基于XML的标记语言,用于在.NET框架中定义和描述用户界面。XAML提供了一种声明性的方式来构建应用程序的UI元素,包括窗口、控件、布局、样式、动画和数据绑定等。 XAML的…...
一篇文章讲透数据结构之二叉搜索树
前言 在前面的学习过程中,我们已经学习了二叉树的相关知识。在这里我们再使用C来实现一些比较难的数据结构。 这篇文章用来实现二叉搜索树。 一.二叉搜索树 1.1二叉搜索树的定义 二叉搜索树(Binary Search Tree)是基于二叉树的一种升级版…...
新手入门c++(8)
到时候了,是时候给你们讲一下其他的定义形式与格式化输入输出了。 1.长整型变量 长整型变量分为两种: ①long类型 在计算机编程中,long 类型是一个整型数据类型,用于存储较大的整数。它的大小和范围取决于操作系统和编译器的实…...
新手铲屎官提问,有哪几款噪音低的宠物空气净化器推荐
相信很多铲屎官都明白的的痛就是猫咪掉毛太严重,所以每次看到满天飞的浮毛时只想赶紧逃离,一点都不想清理。但是家是自己的,猫是自己的,健康也是自己的,不清理也得清理。 为了更有效的清理浮毛,我朋友特意…...
解决RabbitMQ脑裂问题
文章目录 前言一、现象二、解决办法 前言 RabbitMQ脑裂 一、现象 RabbitMQ镜像群出现脑裂现象,各个节点的MQ实例都“各自为政”,数据并不同步。 二、解决办法 # 停止mq sh rabbitmq-server stop_app # 查看mq进程是否存在 ps -ef | grep rabbitmq # …...
经纬恒润AUTOSAR成功适配芯钛科技Alioth TTA8车规级芯片
在汽车电子领域,功能安全扮演着守护者的角色,它确保了车辆在复杂多变的情况下保持稳定可靠的运行。随着汽车电子的复杂性增加,市场对产品功能安全的要求也日益提高。基于此背景,经纬恒润AUTOSAR基础软件产品INTEWORK-EAS-CP成功适…...
4、java random随机数、一维数组、二维数组
目录 Random类与随机数生成数组的概述与使用数组的内存分配与访问数组的常见问题与解决方案一维数组的遍历与操作二维数组的概述与遍历1. Random类与随机数生成 引言 在编程中,我们经常需要生成随机数,比如在游戏、模拟实验或者数据处理中。Java提供了一个非常方便的类Rand…...
C++ 魔法三钥:解锁高效编程的封装、继承与多态
快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 目录 💯前言 💯封装 1.封装概念 2.封装格式 3.封装的原理 4.封装的作用 💯继承 1.继承的概念 2.继承格式 3.继承的…...
姿态传感器(学习笔记上)
上节我们学的是温湿传感器,这节我们学的是姿态传感器,虽然都是传感器,但是它们还是有很大的区别的,这节的传感器我们通过学习可知,开发板上的姿态传感器型号是QMI8658C,内部集成3轴加速度传感器和3轴陀螺仪…...
labelimg使用教程
快捷键 W:调出标注的十字架,开始标注 A:切换到上一张图片 D:切换到下一张图片 del:删除标注的矩形框 CtrlS:保存标注好的标签 Ctrl鼠标滚轮:按住Ctrl,然后滚动鼠标滚轮,…...
力扣21 : 合并两个有序链表
链表style 描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例: 节点大小相同时,l1的节点在前 何解? 1,遍历两个链表,挨个比较节点大小 同时遍…...
【Spring】Spring Boot 配置文件(7)
本系列共涉及4个框架:Sping,SpringBoot,Spring MVC,Mybatis。 博客涉及框架的重要知识点,根据序号学习即可。 有什么不懂的都可以问我,看到消息会回复的,可能会不及时,请见谅!! 1、配置文件作…...
《向量数据库指南》——解锁Wikipedia文章向量的跨语言搜索秘籍
嘿,各位向量数据库和AI应用的小伙伴们,我是你们的老朋友王帅旭,大禹智库的向量数据库高级研究员,也是《向量数据库指南》的作者。今天,咱们来聊聊一个超棒的数据集——百万条 Wikipedia 文章向量,这可是我在研究过程中发现的一个宝藏啊! 首先,咱们得说说这个数据集的来…...
【力扣 + 牛客 | SQL题 | 每日5题】牛客SQL热题204,201,215
1. 力扣1126:查询活跃业务 1.1 题目: 事件表:Events ------------------------ | Column Name | Type | ------------------------ | business_id | int | | event_type | varchar | | occurrences | int | --------…...
下载数据集用于图像分类并自动分为训练集和测试集方法
一、背景 最近需要用Vision Transformer(ViT)完成图像分类任务,因此查到了WZMIAOMIAO的GitHub,里面有各种图像处理的方法。而图像处理的前期工作就是获取大量的数据集,用于训练模型参数,以准确识别或分类我…...
Python xlrd库介绍
一、简介 xlrd是一个用于读取Excel文件(.xls和.xlsx格式)的Python库。它提供了一系列函数来访问Excel文件中的数据,如读取工作表、单元格的值等。 二、安装 可以使用以下命令安装xlrd库: pip install xlrd 三、使用方法 1. 导入库: 示例…...
Javascript立即执行函数
//立即执行函数 把函数的声明看作一个整体声明结束就立即调用 // (function(){console.log(hello) // })(); console.log((function (){ return 0; })()); // let afunction(){ console.log(hello) }; console.log(typeof a);//function,数组:objeck...
Linux相关概念和易错知识点(17)(文件、文件的系统调用接口、C语言标准流)
目录 1.文件 (1)文件组成和访问 (2)文件的管理 (3)C语言标准流 (4)struct file ①文件操作表 ②文件内核缓冲区 (5)Linux下一切皆文件 (…...
三防加固工业平板国产化的现状与展望
在当今全球科技竞争日益激烈的背景下,工业4.0和智能制造的浪潮推动了工业自动化设备的迅速发展,其中,三防加固工业平板电脑作为连接物理世界与数字世界的桥梁,其重要性不言而喻。所谓“三防”,即防水、防尘、防震&…...
3.1.3 看对于“肮脏”页面的处理
3.1.3 看对于“肮脏”页面的处理 文章目录 3.1.3 看对于“肮脏”页面的处理再看对于“肮脏”页面的处理MmPageOutVirtualMemory() 再看对于“肮脏”页面的处理 MmPageOutVirtualMemory() NTSTATUS NTAPI MmPageOutVirtualMemory(PMADDRESS_SPACE AddressSpace,PMEMORY_AREA Me…...
学 Python 还是学 Java?——来自程序员的世纪困惑!
文章目录 1. Python:我就是简单,so what?2. Java:严谨到让你头疼,但大佬都在用!3. 到底谁更香?——关于学哪门语言的百思不得姐结论——到底该选谁?推荐阅读文章 每个程序员都可能面…...
Spring Web MVC 入门
1. 什么是 Spring Web MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从从⼀开始就包含在Spring框架中。它的 正式名称“SpringWebMVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为"Spring MVC". 什么是Servlet呢? Ser…...
吃牛羊肉的季节来了,快来看看怎么陈列与销售!
一、肉品陈列基本原则 (一)新鲜卫生 1、保证商品在正确的温度、正确的方式下陈列 (1)正确的温度:冷藏柜-2℃-2℃;冷冻柜库-20℃-18℃ (2)正确的方式: 商品不遮挡冷气出风口&…...
租房业务全流程管理:Spring Boot系统应用
摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了租房管理系统的开发全过程。通过分析租房管理系统管理的不足,创建了一个计算机管理租房管理系统的方案。文章介绍了租房管理系统的系统分析部分&…...
wordpress+小米商城/seo网站编辑优化招聘
The King’s Ups and Downs (线性DP) [link](Problem - 4489 (dingbacode.com)) 题意 给你n个身高不同的人,问你这些人按照波浪形排列有多少种情况? 波浪形类似于 /\ /\ /\ /\ / 题解 首先要想一种方式可以不重不漏的把所…...
网站建设验收需要注意什么/windows优化大师官方免费
模型推理详细步骤 模型加载步骤 首先,模型加载总共分为三步,第一步加载网络结构,需要和你训时的network结构一样。 model Model.FeedBack3(cfg, config_pathNone, pretrainedTrue).to(device)第二步,加载训练好的参数…...
网上客服软件/关键词优化推广公司
分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击http://www.captainbed.net 事实上,Python 程序在执行过程中同样需要编译(Compile)ÿ…...
做公众号模板的网站/win10系统优化工具
原文:http://www.blogcn.com/User8/flier_lu/blog/29042138.html1.3 局部重绘模式的服务器端响应 在第一小节中,我们曾提到 ScriptManager 在重载的 Web.UI.Control.OnInit 事件中,会根据页面请求中 delta true 是否存在,判断当…...
做网站便宜的公司/百度关键词seo排名优化
day08 软件系统体系结构 常见软件系统体系结构B/S、C/S 1.1 C/S C/S结构即客户端/服务器(Client/Server),例如QQ; 需要编写服务器端程序,以及客户端程序,例如我们安装的就是QQ的客户端程序; 缺…...
优秀网站制作/全国疫情最新报告
#写一个自动生成密码文件的程序 # 1 输入几,文件里面就产生多少条密码 input #2 密码必须包含 大写字母 小写字母 数字 特殊字符 #3 密码不能重复 #4 密码都是随机产生的 #5 密码长度6-11位import string,random pwd_len input(请输入你要产生多少条密码…...