【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 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
尚学教辅学习资料2 小时前
基于SpringBoot+vue+uniapp的智慧旅游小程序+LW示例参考
vue.js·spring boot·uni-app·旅游
Evaporator Core2 小时前
微信小程序数据绑定与事件处理:打造动态交互体验
微信小程序·小程序·交互
IT、木易2 小时前
跟着AI学vue第五章
前端·javascript·vue.js
薛定谔的猫-菜鸟程序员2 小时前
Vue 2全屏滚动动画实战:结合fullpage-vue与animate.css打造炫酷H5页面
前端·css·vue.js
春天姐姐3 小时前
vue3项目开发总结
前端·vue.js·git
烂蜻蜓6 小时前
前端已死?什么是前端
开发语言·前端·javascript·vue.js·uni-app
谢尔登7 小时前
Vue 和 React 的异同点
前端·vue.js·react.js
祈澈菇凉12 小时前
Webpack的基本功能有哪些
前端·javascript·vue.js
少年姜太公12 小时前
搞懂面试常考的watch和watchEffect,看这篇文章就够了
前端·javascript·vue.js