惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
S
Securelist
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
量子位
博客园 - Franky
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
T
Troy Hunt's Blog
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
小众软件
小众软件
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
WordPress大学
WordPress大学
L
Lohrmann on Cybersecurity
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
P
Privacy International News Feed
Last Week in AI
Last Week in AI
Microsoft Security Blog
Microsoft Security Blog
T
Tailwind CSS Blog
博客园_首页
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
D
DataBreaches.Net
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
罗磊的独立博客
Engineering at Meta
Engineering at Meta
Forbes - Security
Forbes - Security
T
Tenable Blog

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
分享两种实现前端拖拽排序的方案 - 元视角
飞鸿踏雪 · 2019-03-31 · via 元视角

Hi,大家好,在经历了两周多的 “写 Bug”、“改 Bug” 死循环后,又一个迭代终于在习以为常的加班生活中结束啦!联想到最近在 Github 上发起的 “996.icu” 事件,不禁令人由衷地感慨生活不易,所谓“”起风了,唯有努力生存”。其实,我反对是加班常态化所导致的无效加班,既然努力工作是为了更好的生活,可如果因此而模糊了工作和生活的界限,这到底是一件好事还是一件坏事呢?想想每个周末被工作群里消息支配的失落感,我希望我有可以自由支配的时间,即使我看起来比别人年轻,即使我下班后依旧孤身一人,因为用时间来换钱这件事情,着实是件性价比不高的事情,货币会一天天地贬值直至我们老去,可那些失去的时间就永远地失去了。好了,“业精于勤荒于嬉”,今天我们来说前端中实现拖拽排序这件事情。

其实,这件事情说起来挺尴尬的,我们曾经为用户提供过某种**”智能“**的体验,我们通过对用户的行为进行分析,为其推荐了个性化的菜单项,甚至根据用户的使用频率对菜单进行了排序。可事实上用户的反响并不是非常强烈,在经过一段时间的使用后,用户依然觉得这个功能相当地”鸡肋“,这件事情告诉我们一个真相,即无论是产品设计还是需求研讨,最好不要轻易地代入用户的角色。最终的结果是我们打算为用户提供自定义的功能,考虑到操作的便利性问题,我们放弃了那种通过上下箭头按钮进行排序的方案,这样就回到了本文的主题,如何在前端中对一组列表进行拖拽排序,最终我们选定了两组方案,它们分别是NestableSortable

Nestable 方案

Nestable 是一个基于 jQuery 的插件,是一个在 Github 上开源的项目,据作者声称,这是一个"拖放具有鼠标和触摸兼容性的分层列表"的方案。这里针对触摸兼容性的支持可以忽略不计,因为如今都 9012 年了,博主依然在做传统前端页面的开发,这里博主最感兴趣的一点是,它可以支持分层列表,换言之,我们的列表元素是可以有层级关系、是可以嵌套的,唯一令人有点不爽的就是它依赖 jQuery 了,在这样一个连 Github 和 Bootstrap 都在努力移除 jQuery 的时代,没有 jQuery 的历史包袱,意味着我们可以大胆地去做现代前端应该做的事情。好了,我们来看看 Nestable 具体是怎么使用的吧!首先,我们定义一个简单的 HTML 结构:

<div class="dd">
    <ol class="dd-list">
        <li class="dd-item" data-id="1">
            <div class="dd-handle">Item 1</div>
        </li>
        <li class="dd-item" data-id="2">
            <div class="dd-handle">Item 2</div>
        </li>
        <li class="dd-item" data-id="3">
            <div class="dd-handle">Item 3</div>
            <ol class="dd-list">
                <li class="dd-item" data-id="4">
                    <div class="dd-handle">Item 4</div>
                </li>
                <li class="dd-item" data-id="5">
                    <div class="dd-handle">Item 5</div>
                </li>
            </ol>
        </li>
    </ol>
</div>

接下来,我们可以使用如下的 JavaScript 代码来初始化整个列表,果然,一股 jQuery 风扑面而来:

$('.dd').nestable({
  /* config options */
});

然后,我们就可以看到下面的效果:

nestablejs-demo nestablejs-demo

怎么样?看起来效果还不错吧!不过博主在前期调研的过程中发现,它对于复杂的层级关系就无能为力啦,可能是博主打开的姿势不对吧!如果希望对列表做更深层次的定制,它需要配置的属性会非常非常的多,而且它有一套内在约束在里面,譬如 className、nodeName 等等,虽然这些都可以去配置,但要想像作者一样运用得好,依然是需要花费大量时间来学习它的 API。

说到这里,对于 Nestable,我唯二喜欢的一个 feature 是,它可以实时地获取到排序后的节点信息,而且是序列化后的 JSON 格式哦,因为当我们要保存用户的排序结果时,有这样一个接口简直太棒啦有木有!这里需要说明的是,所有具备类似**data-**属性的节点都可以被序列化,熟悉前端的朋友一定知道,这是一个 HTML5 中的扩展功能,可以让我们在节点上附带更多的数据信息,在 Bootstrap 中经常需要用到这一特性。

$('.dd').nestable('serialize');

继续以这个例子为例,我们将会得到下面的 JSON 信息:

[{"id":1},{"id":2},{"id":3,"children":[{"id":4},{"id":5}]}]

不过,遗憾的是,貌似作者已经不打算维护这个项目啦,最后一次维护时间已经是 6 年前,毕竟属于 jQuery 的辉煌时代都已经过去,何况是基于 jQuery 的一个插件呢?可这种频繁修改 DOM 结构引发浏览器重绘的操作,在大前端时代会消失吗?或许并不会。关于这个项目更多的使用细节,大家可以到它的Github主页去了解。

Sortable 方案

Sortable相比 Nestable 好的一点就是,它对自己的定位是“一个用于可重新排序的拖放列表的 JavaScript 库”。它不再局限于 jQuery 这样一个方案上,事实上它支持 Vue、React、Angualr、Knockout 等将近 7 个框架,除了支持常规的列表以外,还支持 Grid 中元素的拖拽,文档相比 Nestable 要更为完善一点,所以要在项目中使用的话,我个人更推荐 Sortable。我们一起来看看如何使用 Sortable 吧,这里我们选择 Bootstrap 作为基础样式。首先,我们写一个简单的“列表组”:

<ul class="list-group" id="items">
	<li class="list-group-item" data-id="0">
    	Menu1
	</li>
    <li class="list-group-item" data-id="1">
    	Menu2
    </li>
    <li class="list-group-item" data-id="2">
    	Menu3
    </li>
    <li class="list-group-item" data-id="3">
    	Menu4
    </li>
</ul>

接下来,我们通过 JavaScript 来给这个列表“施加”魔法——巴拉能量:

var ele = document.getElementById('items');
var sortable = Sortable.create(ele);

然后我们就可以发现,这个基于 Bootstrap 的列表居然可以拖拽啦!

sortablejs-demo-1 sortablejs-demo-1

OK,我们继续给这个例子来点魔法,可以让列表元素在拖动的时候高亮显示:

var sortable = Sortable.create(ele, {
	animation: 150,
	ghostClass: 'blue-backgroun-class'
});

可以注意到,拖拽时动画会变得更流畅,被拖拽的元素会以蓝底白字高亮显示:

sortablejs-demo-2 sortablejs-demo-2

和 Nestable 类似,我们可以指定一个回调函数来获得排序后的结果,注意到我们这里指定一个 dataIdAttr,它告诉 Sortable 我们将用哪一个值作为数据的主键,从 data-text 这个命名就可以看出,它的数据是维护在类似**data-**的属性上的,假设我们这里希望获得排序后的菜单,那么,它的打开方式是这样的:

var ele = document.getElementById('items');
var result = document.getElementById('result');
var sortable = Sortable.create(ele, {
	animation: 150,
	dataIdAttr: 'data-text',
	onUpdate: onUpdate,
	ghostClass: 'blue-backgroun-class'
});

function onUpdate(evt){
	var data = sortable.toArray();
	result.innerText = "当前排序结果为:" + JSON.stringify(data);
}

好了,现在可以看到,随着我们对列表进行拖拽,每次都会获得更新以后的列表数据,显然,我们可以将这个结果存到任何地方,这样就可以按用户定义的方式去加载一个列表。

sortablejs-demo-3 sortablejs-demo-3

以上就是 Soratble 的基本用法,关于更多的使用细节,官方文档了解一下。

HTML5 原生方案

OK,说完了 Nestable 和 Sortable 这两个第三方的解决方案,下面我们来说说基于 HTML5 的原生方案。HTML5 标准问世以来,有很多有意思的东西被吸收到标准之中,拖放(drag & drop)就是其中之一。在此之前,我们需要写大量的 JavaScript 代码来实现这个功能。现在,HTML5 中原生支持拖放 API,我们不妨考虑通过它来实现一个可拖拽的列表,这里我们继续沿用基于 Bootstrap 的例子。

var dragElement = null;
var source = document.querySelectorAll('.list-group-item');

for(var i = 0; i < source.length; i++){
    source[i].addEventListener('dragstart',function(ev){
        dragElement = this;
    },false);

    source[i].addEventListener('dragenter', function(ev){
        if(dragElement != this){
            this.parentNode.insertBefore(dragElement,this);
        }
    }, false)

    source[i].addEventListener('dragleave', function(ev){
        if(dragElement != this){
            if(this == this.parentNode.lastElementChild || this == this.parentNode.lastChild){
                this.parentNode.appendChild(dragElement);
            }
        }
    }, false)
};

document.ondragover = function(e){e.preventDefault();}
document.ondrop = function(e){e.preventDefault();}

这里唯一需要注意的地方,就是要给每一个 className 为 list-group-item 的元素添加 draggable 属性,并设置该属性为 true,这是使用 HTML5 拖放 API 的一个前提,换言之,只有 draggable 的元素才可以被拖拽。那么,HTML5 中针对拖放的 API 有哪些呢?针对拖放事件,我们可以抽象出三种角色,它们分别是:

  • 源对象:即对拖拽的对象。它有 dragstart、drag 和 dragend 三个事件。

  • 过程对象:即被拖拽的对象,在拖拽过程中经过的中间对象,它有 dragenter、dragover 和 dragleave 三个事件。

  • 目标对象:即被拖拽的对象,最终所放置的对象,它只有一个 drop 事件。

而在所有的拖拽事件中,都提供了一个数据传递对象 dataTransfer,用于在源对象和目标对象间传递数据。例如,我们可以通过 setData()来向 dataTransfer 存入数据,通过 getData()来从 dataTransfer 读取数据,通过 clearData()来清理 dataTransfer 中的数据。此外,还可以通过 setDragImage()、effectAllowed 属性 和 dropEffect 属性来设置拖拽过程中的图标、拖放的视觉效果等。这里需要注意的是,IE 浏览器不支持 dataTransfer 对象。了解了这下,我们就可以做出一个**”简陋“**的拖拽排序功能:

sortablejs-demo-4 sortablejs-demo-4

本文小结

这篇文章主要分享了三种实现列表拖拽排序的方案,在技术选型阶段,主要选择 Nestable 和 Sortable 这两种方案,前者对层级节点提供的序列化支持非常好,但经过一番折腾后,发现要想像作者一样用好这个插件,着实是件困难的事情,而且貌似作者已经不再维护这个项目了,最近的代码提交历史大概是 6 年前,毕竟属于 jQuery 的辉煌时代已经过去,何况是一个基于 jQuery 的插件呢?所以,个人不建议在正式项目中使用 Nestable。相比之下,Sortable 的定位要更高一点,它不再局限于某个 UI 框架上,理论上任何前端项目都可以使用,从文档的完整性和易用性上,都要比 Nestable 要更胜一筹。原本一开始打算写这两种方案的,后来觉得 HTML5 中提供了拖拽相关的 API 接口,这种方式不失为一种解决方案。虽然提到 HTML5 就让人联想到兼容性,可都 2019 年了,连浓眉大眼的微软(巨硬)都开始在 Edge 里使用 Chrome 内核了,兼容性问题还算是个问题吗?所以,这篇文章实际上介绍了三种解决方案,具体使用哪一种,大家可以根据实际情况来决定,好啦,这篇博客就写到这里,谢谢大家,晚安!