【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. 在当段落高度少于右侧来源高度时,思考了很久,最开始想的是用相邻段落的高度设置给材料,但是因为不是每个段落都有材料,不一定会准确,加上段落组件层级太深,组件之前取值不好维护,因此放弃了这种方式,选择用相邻材料的高度差值处理。
相关推荐
王哲晓1 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v1 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云1 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
GIS程序媛—椰子2 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
我血条子呢3 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
半开半落3 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt
麦麦大数据3 小时前
基于vue+neo4j 的中药方剂知识图谱可视化系统
vue.js·知识图谱·neo4j
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
理想不理想v4 小时前
vue经典前端面试题
前端·javascript·vue.js