【vue】编辑器段落对应材料同步滚动交互

场景需求

  • 编辑器段落对应显示材料
  • 编辑器滚动时,材料同步滚动
  • 编辑器段落无数据时,材料不显示

实现方法

  1. 编辑器与材料组件左右布局
  2. 获取编辑器高度,材料高度与编辑器高度一致
  3. 禁用材料组件的滚动事件
  4. 获取编辑器段落距离顶部的位置,对应材料的顶部高度与段落一致
  5. 监听编辑器滚动高度,对应材料组件同步滚动
  6. 当段落为空时,材料不显示
  7. 当段落比材料短的时候,对应材料进行下移,避免材料重叠

html代码

javascript 复制代码
// 编辑器中段落代码
<div class="ws-container">
	<div class="ws-result" data-set="第一段" data-set-code="dyd" :style="[normal]">
	  <div :style="[indentbase]">这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段这是第一段</div>
	</div>
	<div class="ws-result" data-set="第二段" data-set-code="ded" :style="[normal]">
	  <div :style="[indentbase]" >这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段这是第二段</div>
	</div>
	<div class="ws-result" data-set="第三段" data-set-code="dsd" :style="[normal]">
	  <div :style="[indentbase]">这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段这是第三段</div>
	</div>
</div>

// 对应段落材料代码
<div class="doc-source" @touchmove.prevent @mousewheel.prevent>
	<div ref="sourceMode">
	  <div class="source-mode" :ref="index" v-for="(item, index) in list" :key="index">
	    <div class="source-mode-item" v-for="(item1, index1) in item" :key="index1" :title="item1.name">
	      <div class="flag"></div>
	      <div class="file">来源:{{ item1.name}}</div>
	    </div>
	  </div>
	</div>
</div>

JS代码

javascript 复制代码
export const SOURCEMODE = [
  { name: '第一段', code: 'dyd' },
  { name: '第二段', code: 'ded' },
  { name: '第三段', code: 'dsd' },
];
// 编辑器更新时
contentChange() {
 const dom = this.editor.document.querySelectorAll('.ws-result');
 SOURCEMODE.forEach(item => {
   let flag = false;
   for (let i = 0; i < dom.length; i++) {
     // 有情节且对应dom有高度则表示有内容
     if (item.name === dom[i].getAttribute('data-set') && dom[i].scrollHeight > 0) flag = true;
   }
   this.$emit('updateSourceFile', item.code, flag);
 });
}
// 编辑器删除段落时,更新材料状态
updateSourceFile(type, flag) {
  if (!flag && this.sourceModeFile[type]) delete this.sourceModeFile[type];
  const sourceKeys = Object.keys(this.sourceModeFile);
  const dom = this.$refs.editor.editor.document.querySelectorAll('.ws-result');
  for (let i = 0; i < dom.length; i++) {
    const dataSet = dom[i].getAttribute('data-set');
    const index = SOURCEMODE.findIndex(sourceItem => sourceItem.name === dataSet);
    if (index > -1) {
      dom[i].style.color = 'rgb(24, 144, 255)';
      if (this.$refs[dom[i].getAttribute('data-set-code')]) {
        let currentOffsetTop = dom[i].offsetTop + 26;
        // 当段落比证据短的时候,整体证据进行下移
        // 思路:1. 判断当前dom对应的证据是否有上一个 sourceKeysIndex - 1 > -1
        // 2.拿到上一个距离顶部的高度,拿到当前距离顶部的高度,currentOffsetTop - prevOffsetTop
        // 3.相减后的数字小于上一个的高度prevHeight,说明会重叠,用prevHeight - gap得到重叠高度
        // 4.将重叠高度加上dom距离顶部的高度给当前证据
        // 5.特殊情况,相减后大于上一个高度时,并且差距小于20,则加上26进行情节区分
        const sourceKeysIndex = sourceKeys.findIndex(source => source === dom[i].getAttribute('data-set-code'));
        if (sourceKeysIndex - 1 > -1) {
          const prevOffsetTop = this.$refs[sourceKeys[sourceKeysIndex - 1]][0].offsetTop;
          const prevHeight = this.$refs[sourceKeys[sourceKeysIndex - 1]][0].clientHeight;
          const gap = currentOffsetTop - prevOffsetTop;
          if (gap < prevHeight) {
            currentOffsetTop = currentOffsetTop + (prevHeight - gap) + 26;
          } else {
            if (gap - prevHeight < 20) {
              currentOffsetTop = currentOffsetTop + 26;
            }
          }
        }
        this.$refs[dom[i].getAttribute('data-set-code')][0].style.top = `${currentOffsetTop}px`;
      }
    }
  }
  const containerDom = this.$refs.editor.editor.document.querySelector('.ws-container');
  if (containerDom) this.$refs.sourceMode.style.height = `${containerDom.scrollHeight}px`;
  // 解决当编辑器不触发滚动时,手动让证据滚动到编辑器滚动位置
  const scrollTop = this.$refs.sourceMode.parentElement.scrollTop;
  if (containerDom && scrollTop !== containerDom.scrollTop) {
    this.editorScroll(containerDom.scrollTop);
  }
  // 编辑器滚动时,材料同步滚动
  this.$refs.editor.openEditorScroll();
},
// 编辑器滚动高度,同步右侧材料滚动
editorScroll(scrollTop) {
 this.$refs.sourceMode.parentElement.scrollTo(0, scrollTop);
},

材料列表数据

javascript 复制代码
"list": {
	"dyd": [
	    { "name": "第一段材料1" },
	    { "name": "第一段材料2" },
	    { "name": "第一段材料3" }
	],
	"ded": [
	    { "name": "第二段材料1" },
	    { "name": "第二段材料2" },
	    { "name": "第二段材料3" }
	],
	"dsd": [
	    { "name": "第三段材料1" },
	    { "name": "第三段材料2" },
	    { "name": "第三段材料3" },
	    { "name": "第三段材料4" }
	]
}

效果图

总结:

  1. 最开始为了减少监听带来的影响,只针对当前操作的段落进行处理,但是后面发现,当操作上一段,下一段有材料时,材料的定位不准确,于是改成了只要段落变化,全部的材料位置计算一次
  2. 在当段落高度少于右侧来源高度时,思考了很久,最开始想的是用相邻段落的高度设置给材料,但是因为不是每个段落都有材料,不一定会准确,加上段落组件层级太深,组件之前取值不好维护,因此放弃了这种方式,选择用相邻材料的高度差值处理。
相关推荐
工业互联网专业11 分钟前
基于springboot+vue的高校社团管理系统的设计与实现
java·vue.js·spring boot·毕业设计·源码·课程设计
白宇横流学长1 小时前
基于SpringBoot+Vue的旅游管理系统【源码+文档+部署讲解】
vue.js·spring boot·旅游
张人玉2 小时前
小白误入(需要一定的vue基础 )使用node建立服务器——vue前端登录注册页面连接到数据库
服务器·前端·vue.js
大大。2 小时前
element el-table合并单元格
前端·javascript·vue.js
杨.某某3 小时前
若依 v-hasPermi 自定义指令失效场景
前端·javascript·vue.js
Lysun0013 小时前
vue2的$el.querySelector在vue3中怎么写
前端·javascript·vue.js
毛毛三由3 小时前
【组件分享】商品列表组件-最佳实践
vue.js
想要打 Acm 的小周同学呀4 小时前
前端Vue2项目使用md编辑器
前端·编辑器·vue2·markdown 语法
海的预约5 小时前
VUE之路由Props、replace、编程式路由导航、重定向
前端·vue.js·智能路由器
大叔_爱编程5 小时前
wx036基于springboot+vue+uniapp的校园快递平台小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计