阅读 42

element-tiptap和vuedraggable的拖拽冲突

今天写项目的时候,遇到一个问题,分享给大家。

场景

我有一个A区域,还有一个B区域。A区域内的Vue组件可以通过Vuedraggable这个框架来拖拽到B区域中。B区域内的Vue组件在标题上使用了element-tiptap组件(用来高级编辑)。然而如果B区与内存在使用了element-tiptap组件的组件。就会出现把 A区域中组件的文本内容拖拽到element-tiptap中。

其实我想要的创建新的组件,而它把我拖拽的组件的文本内容添加到了组件中的element-tiptap中。

问题分析

HTML元素设置了contenteditable="true"之后,或者input输入框之间,默认都是可以把选中的文字拖拽到其他的input的元素或者contenteditable="true"的元素中。

我要做的就是禁止其他页面元素把文本内容拖拽到element-tiptap的组件中,element-tiptap组件的原理就是用的contenteditable="true"来实现高级编辑。

我自己写了一个组件案例,用来验证是否contenteditable="true"的情况下,能通过重写drop事件禁止拖拽:

<script> //导入自定义的组件 import DragDIV from "@/components/DragDIV"; /* eslint-disable */ export default {   name: 'App',   components:{     DragDIV   },   mounted() {     //根据class类名动态的添加drop事件     const divs = document.getElementsByClassName('content')     console.log(divs.length)     for (var i=0;i<divs.length; i++) {       let c_item=divs[i]       console.log(c_item.innerHTML)       c_item.addEventListener("drop",(e)=>{         console.log('chesse')         e.preventDefault()       })     }   } } </script> <template>     <!--引用自定义的组件-->   <DragDIV /> </template> 复制代码

自定义组件:DragDiv.vue

<template>   <div  contenteditable="true" class="content"><p>     姐姐,为什么要告诉这楚枫你的身份?风铃对里雾问道他今日既然帮了忙,之前的恩怨,自然要化解。     与其日后被他发现,还不如我先告诉他。里雾说道我记得你说过他,但你当时不是说,他是你在祖武天河,遇到的小角色吗?     那为何这次遇到,你却又突然告知于我,他可以帮我破阵呢?风铃对里雾问道她与楚枫第一次见面,便是在那行宫之内,但其实在她见到楚枫之前     就已经收到了里雾的通知告关于楚枫要去那行宫,以及楚枫有些特别,风铃可以尝试利用楚枫破解行宫考验等事刚好诅咒之力发作,     楚枫出手相助,虽然只是化解了表面症状,但能做到这一点已经很不简单。</p>   </div>   <div contenteditable="true" @click="handleClick"  class="content">     <p>人生何处不相逢</p>   </div> </template> <script> export default {   name: "DragDIV",   methods:{     handleClick(e){       console.log('handleClick')       // e.currentTarget.setAttribute("contenteditable",true)     },     handleMouse(e){       // e.currentTarget.setAttribute("contenteditable",false)     }   } } </script> <style scoped> .content{   width:400px;   height:800px;   -webkit-user-drag: none;   /*margin:0 auto;*/   float: left;   border:2px solid lightgrey; } </style> 复制代码

实际是生效的,可以通过这种方式来解决这个问题。所以我应该找的是drop事件相关的配置。这里我想了很多方法:

  1. 查找element-tiptap 官方文档和源代码,看看有没有相关的组件配置,用来防止其他页面元素拖拽文件进来。onDrop方法前后打印当前的值,结果是相同的,无法解决当前问题。

  2. csdn站内包括其他网站的解决方案:把HTML元素的draggable="false",我加上试了没有生效。并没有修改drop事件的处理函数。element-tiptap中自定义了drop事件的处理函数。

  3. 实在没办法,只能通过浏览器Debug去分析它的加载流程,包括事件的处理顺序,找到了element-tiptap中,设置contenteditable="true"的div,类名是:ProseMirror,它上面加了两个drop事件的处理函数,对应到它框架代码:

    let event = _event;     let dragging = view.dragging;     view.dragging = null;     if (!event.dataTransfer)         return;     let eventPos = view.posAtCoords(eventCoords(event));     if (!eventPos)         return;     let $mouse = view.state.doc.resolve(eventPos.pos);     let slice = dragging && dragging.slice;     if (slice) {         view.someProp("transformPasted", f => { slice = f(slice); });     }     else {         slice = parseFromClipboard(view, event.dataTransfer.getData(brokenClipboardAPI ? "Text" : "text/plain"), brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse);     }     let move = !!(dragging && !event[dragCopyModifier]);     if (view.someProp("handleDrop", f => f(view, event, slice || Slice.empty, move))) {         event.preventDefault();         return;     }     if (!slice)         return;     event.preventDefault();     let insertPos = slice ? dropPoint(view.state.doc, $mouse.pos, slice) : $mouse.pos;     if (insertPos == null)         insertPos = $mouse.pos;     let tr = view.state.tr;     if (move)         tr.deleteSelection();     let pos = tr.mapping.map(insertPos);     let isNode = slice.openStart == 0 && slice.openEnd == 0 && slice.content.childCount == 1;     let beforeInsert = tr.doc;     if (isNode)         tr.replaceRangeWith(pos, pos, slice.content.firstChild);     else         tr.replaceRange(pos, pos, slice);     if (tr.doc.eq(beforeInsert))         return;     let $pos = tr.doc.resolve(pos);     if (isNode && NodeSelection.isSelectable(slice.content.firstChild) &&         $pos.nodeAfter && $pos.nodeAfter.sameMarkup(slice.content.firstChild)) {         tr.setSelection(new NodeSelection($pos));     }     else {         let end = tr.mapping.map(insertPos);         tr.mapping.maps[tr.mapping.maps.length - 1].forEach((_from, _to, _newFrom, newTo) => end = newTo);         tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)));     }     view.focus();     view.dispatch(tr.setMeta("uiEvent", "drop")); }; 复制代码

这里可以看到它的处理流程还是很复杂,我的需求里面没有拖拽文本到B区域中组件的需求,所以这个事件处理函数可以在它的框架代码中删除。

但是项目下有很多的依赖,暂时只能通过如下方法来解决这个问题:(跳过element-tiptap中的drop事件处理函数)

prosemirror-view/dist/index.js:3403行下加入return;  复制代码


总结

  1. 前端框架的很多高级特性都基于HTML中不常见的属性来实现,在使用框架过程中,应该多去看看它的实现原理,分析关键代码。

  2. 编写简洁的代码。

  3. 虽然是前端的框架,但是现在很多框架都是用TypeScript来实现,也是有OOP的实践在里面,要多考虑对象的行为和特性,不能把对象属性都暴露,也不能都不暴露。

  4. 复用的可能,如果一个框架中组件的复用度不高,引入这个框架,对于项目的后期维护非常不利,想弃用,有依赖 。想重写,需要兼容。


作者:Dillon
链接:https://juejin.cn/post/7172174417718411295


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐