moveable 一个可实现前端海报编辑器的 js 库
目录
- 缘由-胡扯
- 本文实验环境
- 通用流程
- 1.基础移动
- 1.1 基础代码
- 1.1.1 data-* 解释
- 1.2 操作元素创建
- 1.3 css 修饰
- 1.4 cdn 引入
- 1.5 js 实现元素可移动
- 1.6 图片拖拽
- 2.缩放
- 3.旋转
- 4.裁剪
懒得改文案了,海报编辑器换方案了,如果后面用别的再更。
缘由-胡扯
导火索:睡不着,现在是 25/1/25 快早上了
在这段时间突然有了一个新项目,该项目与前端通过 img 素材编辑一个海报有相同的需求点,又或者说该需求是弱于海报编辑的。
最开始,我是打算直接通过 js 直接实现对应的功能,但是总有一些小bug,刚好公司的前端推给我了 moveable
,瞟了一眼后,发现需求完美符合,moveable
可实现前端对单个、多个元素的拖拽、组合、编辑、缩放、旋转、拉扯等操作,甚至于有一种“杀鸡焉用牛刀之感”。
但随着项目的迭代,未来的需求不可得知,但用发展的眼光看待这个项目的话,直接实现一个海报编辑器是最优的选择。
接下来我把我总结的 moveable
编写为教程,包括海报编辑器的制作写在该文之中,计划上下两篇,毕竟一篇使用基础,另一篇就是编辑器的制作。
想必,各位也不想在二次开发的时候看不懂 moveable
的实现逻辑吧,能平滑过渡就平滑过渡吧,大家的脑子都不想承载过多的计算,张飞:俺也一样。
本文实验环境
系统:Windows
前端:html
框架:无
js:其实我不是前端,就当是原生吧,因为笔者并不熟悉标准
编辑器:vs code
参考示例:https://daybrush.com/moveable/storybook/index.html
GitHub:https://github.com/daybrush/moveable
apidoc:https://daybrush.com/moveable/release/latest/doc/
通用流程
首先在此通过 vs 编写一个基础的 html (快捷键感叹号会自动弹出,选择单感叹号即可):
此时选择之后将会创建一个基础的 html 基础代码(如果没有的就直接复制吧):
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body></body>
</html>
1.基础移动
1.1 基础代码
我们首先实现 moveable 对元素的基础移动。
要实现 moveable 的基础移动需要创建一个 div
为其根容器,此时我们在 body
元素下创建一个 div
,并给与样式修饰 class="root"
,此处的样式修饰并不是必须项,在此只是为了更好的进行演示,当然,在 storybook 上给与的官方示例也是如此。
此时在基础的 html 元素中的 body
下,创建一个 div
为根节点,代码如下:
<div class="root"></div>
那么此时根节点有了,接下来就需要创建 moveable 的容器。你听的没错,这是一个比较通用的概念,实现某个特殊的元素时,使用一个容器作为存储是一个非常常见的方式。
此时在根节点下,创建一个 div
为容器:
<div class="root"><div class="container" data-croffle-ref="element$0"></div>
</div>
在以上的 html 代码中,样式修饰为 class="container"
则是容器的元素节点,但可能你对这个节点的疑问在 data-croffle-ref="element$0"
,如果没疑问就更好了,在此给一些不理解这个元素属性的读者做一下解释。
1.1.1 data-* 解释
在以上代码中 data-croffle-ref 为 html5 引入的一种机制,这个机制简单的来说是让开发者在不影响本身元素的语义的情况下,为当前元素增加新的元数据,又或者说开发者自定义了当前元素的属性。
一般 data-*
自定义元数据可以与 document.querySelector
搭配,准确的找到对应的元数据,接下来在编写 js 代码时将会解释这一部分。
在这里,读者可以理解为此时定义了一个 moveable 的容器,并且自定义了一个属性 croffle-ref
,但由于自定义属性的编写方式为带前缀 data-
,即编写为 data-croffle-ref="element$0"
,其中 element$0
为这个自定义属性的值。
1.2 操作元素创建
当创建完操作元素的容器后,接下来创建用于控制的元素,则创建一个拖动、缩放、选择等操作的元素。
毕竟我们的目标就是添加不同的图片到当前编辑器,并对这些图片进行拖拽、旋转、缩放等操作。
此时在容器下添加一个 div 用于存放对应的操作元素,此时 body 下的html 代码如下:
<div class="root"><div class="container" data-croffle-ref="element$0"><div class="target" data-croffle-ref="targetRef">Target</div></div>
</div>
以上代码中,样式修饰为 class="target"
的 div 元素则为我们的操作元素,并且这个元素由于 moveable
的机制,给与了 data-croffle-ref="targetRef"
的自定义属性,并且这个 div 的内容为 Target
文本。
1.3 css 修饰
其实也不需要修饰,但是没有样式的话可能读者会觉得很奇怪,读者可以将 css 删除查看效果,最后发现还是加上 css 的为好(示例摘抄于 storybook 但做了精简和增加了便于文章讲解的额外内容)。
css 直接复制在 head 上即可,在此处已经给与了 style 标签:
<style>
.root {position: relative;
}.container {position: relative;margin-top: 50px;
}.target {position: absolute;width: 100px;height: 100px;top: 150px;left: 100px;line-height: 100px;text-align: center;background: #ee8;color: #333;font-weight: bold;border: 1px solid #333;box-sizing: border-box;
}
</style>
以上标签中,给与了 root 根元素与容器 root 的定位方式为相对定位,接着给与了操作元素 target 的样式为一个黄色的矩形框。
css 的话我不做多的解释,其实这是布局设计问题,咱们若不是前端就直接看详细的功能实现即可,若是前端页不用说对吧,如果不懂的直接复制即可,毕竟这是个样式,没有设计到特效动效制作。
接下来 cdn 引入后会给出这一部分的代码:
1.4 cdn 引入
以下是 moveable 的 cdn:
<script src="https://cdnjs.cloudflare.com/ajax/libs/moveable/0.53.0/moveable.min.js"integrity="sha512-gFIuV9WCEJeWYkY1ZdJXugypot9ooEtwJf6U8In5JR6z5ZvV1xAvAQe9mQ7IYBXiF9ICXyiCeqgCJzqf64wh7A=="crossorigin="anonymous" referrerpolicy="no-referrer"></script>
如果 cdn 引入有问题,可以在 https://cdnjs.com/libraries/moveable 查看。
此处我也给予 moveable.min.js
的下载地址:TODO
此时的代码(除 js 外应该是这样):
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>csdn 1_bit moveable how to use</title><style>.root {position: relative;}.container {position: relative;margin-top: 50px;}.target {position: absolute;width: 100px;height: 100px;top: 150px;left: 100px;line-height: 100px;text-align: center;background: #ee8;color: #333;font-weight: bold;border: 1px solid #333;box-sizing: border-box;}</style>
</head><body><div class="root"><div class="container" data-croffle-ref="element$0"><div class="target" data-croffle-ref="targetRef">Target</div></div></div>
</body></html>
1.5 js 实现元素可移动
在 moveable 中,若想让一个元素可移动其实很简单,在此我先列出代码:
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);const moveable$0 = new Moveable(element$0, {target: targetRef,draggable: true,throttleDrag: 1,edgeDraggable: false,startDragRotate: 0,throttleDragRotate: 0}
);moveable$0.on("drag", e => {e.target.style.transform = e.transform;
});
首先我们看最开始的两行,这两行都是通过 document.querySelector 寻找元素,其中一个元素是[data-croffle-ref="element$0"]
为容器元素,另一个为 [data-croffle-ref="targetRef"]
为操作元素。
容器元素与操作元素的获取这两者是必不可少的,通过确定这俩者从而进行控制。
之前也说过 document.querySelector
与自定义元素搭配即可精确找到元素,在此列出代码后不再赘述了。
在找到元素后,直接 new 一个 Moveable,其中第一个参数传入的是在 html 中定义的 element$0
;第二个参数则是操作参数,则你需要如何操作这个元素。
第二个参数为一个字典,在字典中指定了操作的元素 target
为 targetRef
,当前全部 key 键的解释如下:
target
操作的目标元素draggable
目标元素是否可拖动throttleDrag
拖动的延时毫秒数,设置为 0 则表示实时更新edgeDraggable
表示目标的边缘是否可拖动startDragRotate
当鼠标旋转多少后才使元素进行转动throttleDragRotate
旋转的延时毫秒数,设置为 0 则表示实时更新
你可能在思考,当前操作只是移动目标元素,并没有进行旋转等操作,为什么会有这些参数。
其实当前示例时摘抄于 storybook 之上,在此列出为方便 查看 storybook 演示的读者作为解释,当前示例并不需要那么多的参数,只需要传入如下字典即可实现拖拽操作:
{target: targetRef,//目标元素draggable: true//是否可拖拽
}
那么最后的代码:
moveable$0.on("drag", e => {e.target.style.transform = e.transform;
});
则表示为 Moveable 实例的 drag 事件添加一个事件监听器,当目标元素被拖动时,会触发这个事件监听器;其中代码为 e.target.style.transform = e.transform;
则表示事件对象 e 包含了拖动操作的相关信息,其中 e.transform 是拖动后的变换样式,将 e.transform 的值赋给目标元素的 style.transform 属性,更新目标元素的位置。
此时当前拖拽的所有代码如下(省略了 style 样式,直接复制在 1.3 中的 css 即可):
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>csdn 1_bit moveable how to use</title><script src="https://cdnjs.cloudflare.com/ajax/libs/moveable/0.53.0/moveable.min.js"integrity="sha512-gFIuV9WCEJeWYkY1ZdJXugypot9ooEtwJf6U8In5JR6z5ZvV1xAvAQe9mQ7IYBXiF9ICXyiCeqgCJzqf64wh7A=="crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head><body><div class="root"><div class="container" data-croffle-ref="element$0"><div class="target" data-croffle-ref="targetRef">Target</div></div></div><script>const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);const moveable$0 = new Moveable(element$0, {target: targetRef,draggable: true});moveable$0.on("drag", e => {e.target.style.transform = e.transform;});</script>
</body></html>
此时效果如下:
你可能会想,如果咱们可是使用的是图片,并不是文字,不要担心,图片他来了。
1.6 图片拖拽
此时在 html 中,操作元素是通过一个容器进行包裹的,既然是容器,那么是包裹整个操作元素的,那么此时我们只需要将操作元素下添加一个图片即可。
例如在操作元素下添加一个 img 标签:
<div class="target" data-croffle-ref="targetRef"><img src="1.jpg" alt="" style="width: 100px;height: 100px;">
</div>
此时我给与了这个 img 的样式修饰 style="width: 100px;height: 100px;"
,此时的样式修饰是为了满足操作元素的大小,否则将会超出(虽然不会影响当前的功能,但美观上接收不了,之后将会用更加“优雅”的方式解决这个问题)。
图片各位自己放到自己的路径下即可,在此不在赘述,此时的结果如下:
2.缩放
注意:以下 html 代码与 第 1 小点 相同
使用 moveable 还可以对添加的图片进行缩放,只需要 new 一个Moveable 时传入不同的参数即可:
const moveable$0 = new Moveable(element$0, {target: targetRef,scalable: true,// 启用缩放功能,设置为 true 表示允许对目标元素进行缩放操作keepRatio: false,// 将 keepRatio 变量的值赋给配置对象的 keepRatio 属性,控制缩放时是否保持宽高比renderDirections: renderDirections
});
其中参数定义如下:
- scalable 启用缩放功能,设置为 true 表示允许对目标元素进行缩放操作
- keepRatio 控制缩放时是否保持宽高比
- false为不保持 renderDirections 控制显示缩放的控制点
其中 scalable 与 keepRatio 都比较好理解,只有 renderDirections 较为陌生。
renderDirections 是一个 list,这个list 存储了要显示控制点的内容,例如这个 renderDirections 的代码为 const renderDirections = ["n", "s"];
则表示在北部和南部有一个控制点,即如下图所示:
此时具体 js 代码部分如下:
const renderDirections = ["n", "s"];
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);
const moveable$0 = new Moveable(element$0, {target: targetRef,scalable: true,// 启用缩放功能,设置为 true 表示允许对目标元素进行缩放操作keepRatio: false,// 控制缩放时是否保持宽高比renderDirections: renderDirections
});moveable$0.on("scale", e => {e.target.style.transform = e.drag.transform;
});
其中代码:
moveable$0.on("scale", e => {e.target.style.transform = e.drag.transform;
});
为表示监听 scale 缩放事件,通过 e.drag.transform 得到最新的样式信息给予到目标元素即可改变以及重新绘制。
此时还可以更改 renderDirections 为 const renderDirections = ["nw","n","ne","w","e","sw","s","se"];
,其中 nw 表示东南、ne 表示西南、以此类推运行代码后展现如下:
其展示效果如下:
若将 keepRatio: false,// 控制缩放时是否保持宽高比
改成 true,效果如下:
3.旋转
设定目标是否可进行旋转也只是需要 new 一个 moveable 时传入的参数即可:
const moveable$0 = new Moveable(element$0, {target: targetRef,rotatable: true,rotationPosition: rotationPosition
});
参数说明如下:
- rotatable 控制目标元素是否可旋转 true 为允许
- rotationPosition 指定旋转控制点的位置
以上代码中 rotationPosition 设置为字符串类型的 val 为 “top” 即可,即:const rotationPosition = "top";
之后为 moveable$0 设置 rotate 事件监听:
moveable$0.on("rotate", e => {e.target.style.transform = e.drag.transform;
});
那么即可设置目标元素的响应。
完整的 js 代码如下:
const rotationPosition = "top";
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);
const moveable$0 = new Moveable(element$0, {target: targetRef,rotatable: true,// 控制目标元素是否可旋转rotationPosition: rotationPosition// 指定旋转控制点的位置
});
moveable$0.on("rotate", e => {e.target.style.transform = e.drag.transform;
});
此时的 html 代码并不需要修改,此时页面所展示的内容如下:
此时的控制点在顶部,若设置 rotationPosition 为 bottom const rotationPosition = "bottom";
时页面展示效果如下:
此时的操作效果如下:
4.裁剪
在 moveable 中,一般情况下,只需要在 new moveable 时传入不同的参数即可对操作目标开启不同的操作,最后再为其添加对应操作的事件响应,即可完成对开启的操作完成监听。
以下是一个完成裁剪功能的 moveable 代码:
const moveable$0 = new Moveable(element$0, {target: targetRef,draggable: true,clippable: true,clipTargetBounds: false,
});
以下为各个新增参数的解释:
- draggable 控制目标元素是否可拖动
- clippable 控制目标元素是否可裁剪
- clipRelative 控制裁剪区域的定位方式
- clipTargetBounds 控制裁剪区域是否受目标元素边界限制
此时读者可能发现,以上再 new moveable 时允许目标可拖动以及目标可剪切,那我该怎么样监听两个事件呢?其实很简单,只需要使用 on 监听两个事件即可,即:
moveable$0.on("drag", e => {e.target.style.transform = e.transform;
});
moveable$0.on("clip", e => {e.target.style.clipPath = e.clipStyle;
});
再 drag 时给与 e.transform 到 e.target.style.transform 的 transform 即可,再 clip 时给与 e.clipStyle 到 e.target.style.clipPath 即可。
接下来我们继续回到参数之中,其中 clipTargetBounds 表示在裁剪时会不会超出区域了还可以裁剪,当设置为 true 时不允许过边界(蓝框裁剪),结果如下:
如果设置为 false则允许过边界,效果如下(也演示了拖拽效果):
此时完整的js 代码如下(html 不需要进行修改):
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);const moveable$0 = new Moveable(element$0, {target: targetRef,draggable: true,clippable: true,clipTargetBounds: false,
});moveable$0.on("drag", e => {e.target.style.transform = e.transform;
});moveable$0.on("clip", e => {e.target.style.clipPath = e.clipStyle;
});
相关文章:

moveable 一个可实现前端海报编辑器的 js 库
目录 缘由-胡扯本文实验环境通用流程1.基础移动1.1 基础代码1.1.1 data-* 解释 1.2 操作元素创建1.3 css 修饰1.4 cdn 引入1.5 js 实现元素可移动1.6 图片拖拽2.缩放3.旋转4.裁剪 懒得改文案了,海报编辑器换方案了,如果后面用别的再更。 缘由-胡扯 导火…...
wangEditor 编辑器 Vue 2.0 + Nodejs 配置
资料 Vue2.0 版本的安装:https://www.wangeditor.com/v5/for-frame.html#%E4%BD%BF%E7%94%A8上传图片配置:https://www.wangeditor.com/v5/menu-config.html#%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87 安装步骤 1.安装界面基础部分 <!-- 富文本编辑器…...

DeepSeek R1生成图片总结2(虽然本身是不能直接生成图片,但是可以想办法利用别的工具一起实现)
DeepSeek官网 目前阶段,DeepSeek R1是不能直接生成图片的,但可以通过优化文本后转换为SVG或HTML代码,再保存为图片。另外,Janus-Pro是DeepSeek的多模态模型,支持文生图,但需要本地部署或者使用第三方工具。…...
x86平台基于Qt+opengl优化ffmpeg软解码1080P视频渲染效率
一般的在arm嵌入式平台,大多数板子都要硬解码硬件渲染的框架,使用即可。 在x86下比较麻烦了。 优化的思路一共有以下几个方面, 1. 软解码变成硬解码 2. 将YUV转QImage的操作转移到GPU 3. QWidget渲染QImage变成opengGL渲染AVFrame 这三点…...
机器学习入门-读书摘要
先看了《深度学习入门:基于python的理论和实践》这本电子书,早上因为入迷还坐过站了。。 因为里面的反向传播和链式法则特别难懂,又网上搜了相关内容进行进一步理解,参考的以下文章(个人认为都讲的都非常好࿰…...
前端【技术方案】重构项目
1. 明确重构目标 优化性能 减少页面加载时间降低资源占用 提升代码可维护性 更规范的代码风格更清晰的代码结构更明确的模块设计 扩展功能 为项目添加新功能改进现有功能 2. 评估项目现状 审查代码 全面检查现有代码,找出代码中的问题,如代码冗余、耦合…...

大语言模型简史:从Transformer(2017)到DeepSeek-R1(2025)的进化之路
2025年初,中国推出了具有开创性且高性价比的「大型语言模型」(Large Language Model — LLM)DeepSeek-R1,引发了AI的巨大变革。本文回顾了LLM的发展历程,起点是2017年革命性的Transformer架构,该架构通过「…...

RabbitMQ服务异步通信
消息队列在使用过程中,面临着很多实际问题需要思考: 1. 消息可靠性 消息从发送,到消费者接收,会经理多个过程: 其中的每一步都可能导致消息丢失,常见的丢失原因包括: 发送时丢失: 生…...
Python常见面试题的详解7
1. 内置的数据结构有哪几种 Python 中有多种内置的数据结构,主要分为以下几种: 1.1 数值类型 整数(int):用于表示整数,没有大小限制。例如:1, -5, 100。浮点数(float)…...

Django REST Framework (DRF) 中用于构建 API 视图类解析
Django REST Framework (DRF) 提供了丰富的视图类,用于构建 API 视图。这些视图类可以分为以下几类: 1. 基础视图类 这些是 DRF 中最基础的视图类,通常用于实现自定义逻辑。 常用类 APIView: 最基本的视图类,所有其…...

Huatuo热更新--安装HybridCLR
1.自行安装unity编辑器 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x 中任一版本。推荐安装2019.4.40、2020.3.26、2021.3.x、2022.3.x版本。 根据你打包的目标平台,安装过程中选择必要模块。如果打包Android或iOS,直接选择相应模块即可。如果你想打包…...
读书笔记 - 修改代码的艺术
读书笔记 - 修改代码的艺术 第 1 章 修改软件第 2 章 带着反馈工作系统变更方式反馈方式遗留代码修改方法 第 3 章 感知和分离伪协作程序模拟对象 第 4 章 接缝模型接缝 第 5 章 工具自动化重构工具单元测试用具 第 6 章 时间紧迫,但必须修改新生方法(Sp…...

【Go并发编程】Goroutine 调度器揭秘:从 GMP 模型到 Work Stealing 算法
每天一篇Go语言干货,从核心到百万并发实战,快来关注魔法小匠,一起探索Go语言的无限可能! 在 Go 语言中,Goroutine 是一种轻量级的并发执行单元,它使得并发编程变得简单高效。而 Goroutine 的高效调度机制是…...
c# -01新属性-模式匹配、弃元、析构元组和其他类型
文章目录 **学习摘抄分享**模式匹配概述Null 检查类型测试比较离散值关系模型多个输入ObServation列表模式弃元元组和对象析构利用switch的模式进行匹配对于out的方法调用独立弃元析构元组和其他类型元组方法一方法二方法三方法四使用弃元元组的元素使用弃元的用户定义类型解构…...
同步异步日志系统-日志落地模块的实现
功能:将格式化完成后的日志消息字符串,输出到指定的位置 扩展:支持同时将日志落地到不同的位置 位置分类: 1.标准输出 2.指定文件(时候进行日志分析) 3.滚动文件(文件按照时间/大小进行滚动…...

LabVIEW 天然气水合物电声联合探测
天然气水合物被认为是潜在的清洁能源,其储量丰富,预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性,天然气水合物的探测面临诸多挑战,涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…...
类型通配符上限
主函数 package typeWildcardTop;import java.util.ArrayList;public class typeWildcardTopTest {/**/public static void main(String[] args) { // test1();test2();}/*测试showList接收ArrayList类型 ArrayList接收各种类型参数创建animals cats mincats集合 传入s…...

嵌入式音视频开发(二)ffmpeg音视频同步
系列文章目录 嵌入式音视频开发(零)移植ffmpeg及推流测试 嵌入式音视频开发(一)ffmpeg框架及内核解析 嵌入式音视频开发(二)ffmpeg音视频同步 嵌入式音视频开发(三)直播协议及编码器…...

Mongodb数据管理
Mongodb数据管理 1.登录数据库,查看默认的库 [rootdb51~]# mongo> show databases; admin 0.000GB config 0.000GB local 0.000GB> use admin switched to db admin > show tables system.version > admin库:admin 是 MongoDB 的管理…...

Django 创建表 choices的妙用:get_<field_name>_display()
1.定义choices 我在创建表时,对于性别这个字段,定义了choices 选项,1代表男,2代表女 mysql中表的数据如下,里面存储的是1或2 如果我们在网页上展示的时候,想展示“男”或“女”,而不是数字1或2…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...