在前端开发中,树形菜单是非常常见的一种组件。在某些场景下,用户需要自主拖拽菜单项来实现顺序的改变或者层级的修改等操作。本文将介绍如何利用 Custom Elements 实现可拖拽的树形菜单,并分享一些遇到的难点和解决方法。
Custom Elements 简介
Custom Elements 是一个 Web 标准,允许开发者扩展 HTML 元素来创建可重用的组件。
与开发其他 Web 组件的方式不同,利用 Custom Elements 可以定义自己的 HTML 元素,并将其看做一个组件进行使用。这个特性使得我们可以快速地开发可重用、可维护的组件,并从中获得代码复用和可扩展性的好处。
实现可拖拽的树形菜单
基本框架
我们的组件需要拥有下面的特点:
- 可以接受树形菜单的数据结构,并展示出来;
- 菜单项可以拖拽,行为包括限制拖拽到指定区域、拖动时菜单项随着鼠标移动、释放鼠标后菜单项在新的位置呈现等;
- 通过监听事件实现拖放操作。
下面我们将根据这些特点搭建组件的基本框架。
<drag-drop-tree> <ul class="tree"></ul> </drag-drop-tree>
在上面的示例中,我们定义了一个 drag-drop-tree
元素,并在其中添加了一个 ul
元素来展示树形菜单。接下来,我们需要构建树形菜单。在传统的 HTML 中,我们可以使用 <li>
和 <ul>
标签来构建树形结构,而在这里,我们需要通过 JavaScript 来实现它。
构建树形结构
我们可以使用 appendChild
方法将树形结构添加到 ul
元素中。在添加元素时,我们需要先遍历数据,然后将每个节点转化为菜单项。
-- -------------------- ---- ------- ------------------- - ------------------------------ ------- ---------------------------------- --------------------- --------------------------------- -------------------- ----------------------------- ---------------- -------------------------------- ------------------- -- ------------------ - --------- - -------------------------- ------------------------------------------------------------- - - ---------------- - ----- -- - ---------------------------- ---------------------- ----------------- -- - ----- -- - ---------------------------- --------- - ----- ------------ - - ---- ------------ ---------------- --------------------- ------------------------- ------ - -- ----------------------------- -- -------------------- - -- - ----- -------- - ------------------------------ ------------------------ -------------------------------- - ------------------ -- ------ -- -
以上的代码逻辑比较简单。我们首先使用 connectedCallback
方法绑定事件和设置元素属性。接下来,在非空的情况下,我们调用 renderTree
方法来渲染树形结构。renderTree
方法遍历数据,并将每个节点转化为菜单项。如果节点包含子节点,那么递归调用 renderTree
方法,将子节点渲染到 li
中。最后将 li
添加到 ul
中,并返回它。
实现拖拽行为
接下来,我们需要为菜单项实现拖拽的行为。在 HTML 中,我们可以将 draggable
属性设置为 true
来使元素可以拖拽。我们还可以通过绑定事件处理程序来跟踪拖拽事件,比如 dragstart
、dragover
、dragend
等。
下面是拖拽行为的具体实现代码:
-- -------------------- ---- ------- --- - ------------------------------ ---- -- ------------------ - --------------- - ------------------------ --------------------- - ----------------------------- ---------------------------- - ------ ------------------------------------ ----- ------------------------- - ------ ---------------------- - ----- ------------ - -------- ------------------- - --- - ----------------------- -- ----------------- - -- ------------------ - ------------------ - ------------------------- - ------ ------------------------- - -------- ------------------- ------ ----- - --- - ------------------- --------------------------- -- ------------- - -- ------------------- - ------------------- - -- ------------- --- -------- -- ---------------- --- ------ - ----- -------- - ------------------------ -------------- - ----------------------------- ----- -------- - - --------- ---------------- --------------- ---------------------- --------- --------------- -------- ------------ - ------------------------------------------------- ------------------------- - -- ----- -------- - ------------------- ----- ----------- - -------------------- -- ------------- - ----------------------------------- ------------ - ---- - ---------------------------------- - ------------------------- - ------ ----- - --- - ---------------- -- ---------------- - ---------------------- - -- ------------ - ---- ------------------- - ---------------------- - --- - - - ----- -------- - -- ----------------------------- --- ----- - ----- - ------ - ----------------- - ------ ------------------------------------------------------ - ---------------- - --- - - - ----- -------- - -- ------------------------------ --- ----- - ----- - ------ - ----------------------------- --- - ------ - - -------------------- - ----- - --------- --------- -------- --------------- -------------- - - -------- ----- --------- - -------------------------- --------------- ----- --------- - -------------------------- --------------- ----- ---- - ----------------------------------- ----- ----- ------------- - ----------------------------------------- --------- ----- ---------------------------------------- -- ----- - -------------------- ------------ - --- ------ --------------------- -- - -- ------------ --- -- - ------ - -------- - -- ----------------------------- -- -------------------- - -- - ------ - ------------------------------ ----------- - -- - -- ------ ------ - -------------------------- --------- ----- - --- - - -------- -- ------------------- -- ---------------------------- - --------------- - - -- - -- -- - -- - - - - - ---- -- -- - ---------------- - - - --------------- - ------ - -
在本例中,我们在 handleDragStart
方法中保存了被拖拽元素的索引和父元素索引,同时将拖拽标记 dragged
标为 true
。接下来,在目标元素上拖拽时,我们阻止了默认行为,将 dropEffect
设为 move
,并高亮目标元素。当鼠标松开时,我们计算出被拖拽节点在新位置处的索引并更新数据,并将拖拽标记 dragged
标为 false
。
我们还提供了一些辅助函数来获取位置、计算新的索引和更新数据等。这些函数都是一些较为基础、常见的辅助函数,此处不做详细介绍。
遇到的难点和解决方法
防止拖拽嵌套
遇到的一个问题是,对于含有嵌套子项的菜单,拖拽时会出现拖拽嵌套的情况,此时不能正常拖拽。
为了解决这个问题,我们需要根据当前拖拽的元素,获取其最近的父元素,并判断是否为同一级菜单项。如果拖拽的菜单项是其它菜单项的一项子菜单,那么该元素应该移动到子菜单的父级中。我们可以通过对 DOM 树进行递归遍历,找到该元素对应的父菜单项,获取其索引,并将其移动到新位置。
实时更新数据
当拖拽菜单项时,根据新的位置,我们需要实时更新数据,以便存储到服务器或本地存储中。
为了满足这个需求,我们可以提供一个设置数据的函数,并从该函数中修改数据,同时调用渲染函数来重新渲染整个树形菜单。在实际应用中,我们可以在此处向服务器发送请求,更新菜单项的排序和层级等信息。
示例代码
下面是完整示例代码,其中包含了定义元素、绑定数据、渲染树形结构以及拖拽行为等功能。
-- -------------------- ---- ------- ----- ------------ ------- ----------- - ------------- - ------- --------------- - ------------------------ -------- ------------ - ---- --------------- - -- --------------------- - -- ----------------------------------------- -- -- - ---------------- ------- --- ---- -------- -- - ------ --- -------------------- ------- --------- --- ------ - --- --- - ------------------------- ------ --------------- - --- --------- - ------ --- - ------------------------------ ------- ------- - -- ------- --- ------- - --------- - ------------------ - - ------------------- - ------------------------------ ------- ---------------------------------- --------------------- --------------------------------- -------------------- ----------------------------- ---------------- -------------------------------- ------------------- -- ------------------ - --------- - -------------------------- ------------------------------------------------------------- - - ---------------- - ----- -- - ---------------------------- ---------------------- ----------------- -- - ----- -- - ---------------------------- --------- - ----- ------------ - - ---- ------------ ---------------- --------------------- ------------------------- ------ - -- ----------------------------- -- -------------------- - -- - ----- -------- - ------------------------------ ------------------------ -------------------------------- - ------------------ -- ------ -- - ------------------ - --------------- - ------------------------ --------------------- - ----------------------------- ---------------------------- - ------ ------------------------------------ ----- ------------------------- - ------ ---------------------- - ----- ------------ - -------- ------------------- - ----------------- - -- ------------------ - ------------------ - ------------------------- - ------ ------------------------- - -------- ------------------- ------ ----- - ------------- - -- ------------------- - ------------------- - -- ------------- --- -------- -- ---------------- --- ------ - ----- -------- - ------------------------ -------------- - ----------------------------- ----- -------- - - --------- ---------------- --------------- ---------------------- --------- --------------- -------- ------------ - ------------------------------------------------- ------------------------- - -- ----- -------- - ------------------- ----- ----------- - -------------------- -- ------------- - ----------------------------------- ------------ - ---- - ---------------------------------- - ------------------------- - ------ ----- - ---------------- - ---------------------- - -- ------------ - ---- ------------------- - ---------------------- - --- - - - ----- -------- - -- ----------------------------- --- ----- - ----- - ------ - ----------------- - ------ ------------------------------------------------------ - ---------------- - --- - - - ----- -------- - -- ------------------------------ --- ----- - ----- - ------ - ----------------------------- --- - ------ - - -------------------- - ----- - --------- --------- -------- --------------- -------------- - - -------- ----- --------- - -------------------------- --------------- ----- --------- - -------------------------- --------------- ----- ---- - ----------------------------------- ----- ----- ------------- - ----------------------------------------- --------- ----- ---------------------------------------- -- ----- - -------------------- ------------ - --- ------ --------------------- -- - -- ------------ --- -- - ------ - -------- - -- ----------------------------- -- -------------------- - -- - ------ - ------------------------------ ----------- - -- - -- ------ ------ - -------------------------- --------- ----- - --- - - -------- -- ------------------- -- ---------------------------- - --------------- - - -- - -- -- - -- - - - - - ---- -- -- - ---------------- - - - --------------- - ------ - - - ---------------------------------------------- -------------
总结
Custom Elements 是一个十分实用的标准,它为 Web 开发带来了更多的可能性。利用 Custom Elements,我们可以更快地构建可重用、可扩展的组件,同时实现更丰富的交互效果。本文介绍了如何使用 Custom Elements 实现一个可拖拽的树形菜单,并总结了在开发过程中使用到的一些技巧和经验。
当然,本文所举的例子只是 Custom Elements 的冰山一角,如果你想深入了解 Custom Elements,还需要更多的实践和学习。我们希望本文能给你带来一些科学的指导,使你在利用 Custom Elements 进行 Web 开发时更加得心应手。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/652f73417d4982a6eb094e9d