Code前端首页关于Code前端联系我们

视图+元素大形式解决方案(四)-锚定组件(下)

terry 2年前 (2023-09-08) 阅读数 143 #Vue

系列文章:

  • view+element大表格解决方案(一)——概述
  • view+element大形状解决方案(2)——形状分割
  • 视图+元素大尺寸解决方案(3)-锚定组件(中)

前言

上一篇文章基本实现了锚点组件的功能,剩下一些优化和功能升级在本文中完成。一是优化样式,按照百度百科的样式效果制作;其次,使用组件时使用v-if="pageBlock"的判断,必须隐藏细节;最后,如果锚点很多,锚点应该自动移动到可见范围。

准备

要实现本文的内容,首先在表单组件的公司信息后面添加一些章节,这样锚点的数量就足够了,代码如下:

<div data-section="公司信息"></div>
<form2 ref="form2" :data="formDataMap.form2" />
<!-- 增加占位章节 -->
<div data-section="占位信息1" data-ismain></div>
<div data-section="xxx1"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="xxx2"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="xxx3"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="占位信息2" data-ismain></div>
<div data-section="yyy1"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="yyy2"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="yyy3"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="占位信息3" data-ismain></div>
<div data-section="zzz1"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="zzz2"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
<div data-section="zzz3"></div>
<div style="width:300px;height:200px;background:#ccc;">占位符</div>
 

同时更改.form-wrapper的样式,代码如下:

.form-wrapper {
  position: relative;
  width: 100%;
  // 修改height为合适的演示高度
  height: 500px;
  // 增加背景色,主要是为了方便理解截图
  background: #efefef;
  padding: 16px;
  overflow-y: auto;
  ::v-deep input {
    width: 280px;
  }
}
 

此时效果如下:

image.pngimage.png

image.pngimage.png

下面正式开始。

风格优化

目标图案左侧有节点导轨,导轨上下两端各有一个空心圆;每个主节点都有一个对应的实心圆;当前节点的导轨上有一个三角形指示器。
.anchor-track 添加到原始 .anchor,并将锚点的直接父级更改为 .anchor-list。修改后的模板代码如下:

<template>
  <div class="anchor">
    <div class="anchor-track"></div>
    <div class="anchor-list">
      <div v-for="node in sections" :key="node.label" :label="node.index"
           :class="[node.ismain?'anchor-main-node':'anchor-sub-node',{'anchor-node-active':currentSection===node.label}]"
           @click="handleClick(node.label)">
        {{ node.label }}
      </div>
    </div>
  </div>
</template>
 

对应的款式代码如下:

.anchor {
  position: relative;
  width: 100%;
  height: 100%;
}
.anchor-track {
  position: absolute;
  left: 4px;
  top: -10px;
  bottom: -10px;
  width: 1px;
  background: #aaa;
  // 上下的空心圆圈
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: -4px;
    width: 10px;
    height: 10px;
    border-radius: 10px;
    border: 1px solid #ccc;
    background: #fff;
  }
  &::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: -4px;
    width: 10px;
    height: 10px;
    border-radius: 10px;
    border: 1px solid #ccc;
    background: #fff;
  }
}
.anchor-list {
  position: relative;
  padding: 12px;
  width: 100%;
  height: 100%;
  // 宽高尽量依赖外界容器
  // 如果容器未处理,则使用默认最小值
  min-width: 120px;
  min-height: 120px;
  overflow-x: visible;
  overflow-y: auto;
  // 隐藏滚动条
  &::-webkit-scrollbar {
    display: none;
  }
}
.anchor-main-node {
  position: relative;
  margin: 8px 0;
  font-size: 14px;
  font-weight: bold;
  color: #555;
  cursor: pointer;
  &::before {
    content: attr(label);
    margin-left: 6px;
    margin-right: 6px;
  }
  // 新增实心点
  &::after {
    content: '';
    position: absolute;
    left: -11px;
    top: 3px;
    width: 8px;
    height: 8px;
    border-radius: 8px;
    background: #666;
  }
}
.anchor-sub-node {
  position: relative;
  margin: 8px 0;
  padding-left: 22px;
  font-size: 14px;
  color: #666;
  cursor: pointer;
  &::before {
    content: attr(label);
    margin-right: 4px;
  }
}
.anchor-node-active {
  color: #38f;
  // 新增三角
  &::after {
    content: '';
    position: absolute;
    left: -8px;
    top: 0px;
    width: 0px;
    height: 0px;
    border: 8px solid transparent;
    border-left-color: #38f;
    background: transparent;
    border-radius: 0;
  }
}
 

此时效果如下:

image.pngimage.png

style 部分没什么特别要说的,都是用伪元素以绝对定位的方式指向合适的位置。

删除 v-if

使用锚点组件时,为防止锚点mounted获取形状的圆顶结构,添加了 v-if 语句。这个方案暂时可以用,但是做成正式组件就不适合了,所以想办法把这个V-if去掉。人们很自然地想到组件中的外观属性pageBlock。如果oldValue是null并且newValue有值,则该状态确实已安装。但这样做会破坏汇编的语义并增加代码的不可读性。我采用的方法是在anchor组件文件夹中添加一个组件包装层,并在包装​​层中实现v-if。代码如下:

<template>
  <anchor v-if="pageBlock" :page-block="pageBlock" />
</template>

<script>
import Anchor from './anchor'
export default {
  components: {
    Anchor
  },
  props: {
    pageBlock: HTMLElement
  }
}
</script>

<style scoped lang="scss">
</style>
 

这时可以将form组件中的v-if删除,测试后效果正常。

高亮显示的锚点始终显示

锚点还有一项功能没有实现,就是当章节向左侧滚动时,标记的锚点可以自动显示在面板上。如下图:

image.pngimage.png

如何让锚点自动显示?当然,我们需要监控当前的锚点。如果锚点发生变化,则计算当前锚点在锚点面板中的位置。如果是中下位置,就让它在中间;如果锚点位于上半部分,则立即将面板返回到顶部。
添加的js如下:

watch: {
    currentSection() {
      this.showCurrentSectionsAnchor()
    }
},
// mehthods里增加showCurrentSectionsAnchor方法
showCurrentSectionsAnchor() {
  // 给锚点增加data-anchor属性,便于查找
  const anchor = this.$refs['anchor'].querySelector(`[data-anchor=${this.currentSection}]`)
  if (anchor) {
    const wrapper = anchor.parentElement
    const clientHeight = wrapper.clientHeight
    const offsetTop = anchor.offsetTop
    // 计算当前元素是否处于容器可视区域中间偏下的位置,如果是的,则让容器滚动使得元素可视居中
    if (offsetTop > clientHeight / 2) {
      wrapper.scrollTop = offsetTop - clientHeight / 2
    } else {
      wrapper.scrollTop = 0
    }
  }
}
 

要根据锚文本查找锚点,请将ref="anchor"添加到组件根元素.anchor。当 v-for 生成.anchor-list中的锚点时,每个节点都绑定到:data-anchor="node.label"
测试效果,发现随着表单的滚动,锚点始终显示高亮的节点。但这时候有一个问题。单击锚点时,左侧形状不会滚动。这是怎么回事?我的猜测是,新的 showCurrentSectionsAnchor 方法更改了锚点容器的 scrollTop,该容器基本上是一个滚动。此卷轴与handleClick中的section.scrollIntoView相冲突。在本例中,我向 scrollIntoView 添加了异步执行以避免同时滚动。修改后的handleClick代码如下:

handleClick(label) {
  // 设置当前锚点对应章节
  this.currentSection = label
  // 查找到到该章节的dom
  const section = this.pageBlock.querySelector(`[data-section=${label}]`)
  // 延迟执行
  setTimeout(() => {
    // 平滑滚动至该章节
    section.scrollIntoView({
      behavior: 'smooth',
      block: 'start'
    })
  })
}
 

此时完全正常。好了,关于锚点的时间已经足够了,下一篇文章将回到形状的主题。 感谢阅读,欢迎指正!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门