element-tiptap和vuedraggable的拖拽冲突


highlight: a11y-dark
theme: github

图片[1]-element-tiptap和vuedraggable的拖拽冲突-烟雨网图片[1]-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  mce-contenteditable="true" class="content"><p>
    姐姐,为什么要告诉这楚枫你的身份?风铃对里雾问道他今日既然帮了忙,之前的恩怨,自然要化解。
    与其日后被他发现,还不如我先告诉他。里雾说道我记得你说过他,但你当时不是说,他是你在祖武天河,遇到的小角色吗?
    那为何这次遇到,你却又突然告知于我,他可以帮我破阵呢?风铃对里雾问道她与楚枫第一次见面,便是在那行宫之内,但其实在她见到楚枫之前
    就已经收到了里雾的通知告关于楚枫要去那行宫,以及楚枫有些特别,风铃可以尝试利用楚枫破解行宫考验等事刚好诅咒之力发作,
    楚枫出手相助,虽然只是化解了表面症状,但能做到这一点已经很不简单。</p>
  </div>
  <div mce-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. 复用的可能,如果一个框架中组件的复用度不高,引入这个框架,对于项目的后期维护非常不利,想弃用,有依赖 。想重写,需要兼容。

参考:

GitHub – Leecason/element-tiptap: 🌸A modern WYSIWYG rich-text editor using tiptap and Element UI for Vue2 (🚀 tiptap2 and Vue3 is in alpha)

https://github.com/ueberdosis/tiptap

draggable – HTML(超文本标记语言) | MDN

contenteditable – HTML(超文本标记语言) | MDN

欢迎大家私信,留言学习交流。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容