vue-封装锚点组件

当页面的内容或者文字特别多的时候 我们就需要使用到锚点。但是由于element-ui没有锚点(iview,ant有)。
故自己封装一个锚点页面:
由于锚点的滚动可能和vue路由有冲突,所以我选择自定义函数来实现锚点的滚动
第一种修改路由:
(我没有参考这种方法)

// 例子,自行封装到你項目的代碼
const router = new VueRouter({
  routes,
  mode: 'history',
  scrollBehavior (to, from, savedPosition) {
      // 如果你的链接是帶 # 这种
      // to.hash 就会有值(值就是链接)
      // 例如 #3
      if (to.hash) {
        return {
          // 这个是通过 to.hash 的值來找到对应的元素
          // 照你的 html 來看是不用多加处理这样就可以了
          // 例如你按下 #3 的链接,就會变成 querySelector('#3'),自然會找到 id = 3 的元素
          selector: to.hash
        }
      }
    }
})

第二种自定义滚动函数:

goAnchor(selector) {
        var anchor = this.$el.querySelector(selector)
        document.body.scrollTop = anchor.offsetTop
    }

下面封装的是整个锚点页面

<template>
  <div :class="float ? 'cec-anchor_float' : 'cec-anchor'" :style="{ height }">
    <div :class="`anchor-header ${float ? getHeaderPosition : ''}`">
      <div class="anchor-trunk" :style="{ height: getAnchorLinkHeight }">
        <div class="anchor-icon" ref="anchor-icon">
          <div class="anchor-icon-white"></div>
        </div>
      </div>
      <div class="anchor-link" :style="{ height: getAnchorLinkHeight }">
        <AnchorLink
          :data="data"
          @goAnchor="goAnchor"
          :currentAnchor="currentAnchor"
        ></AnchorLink>
      </div>
    </div>
    <div ref="anchor-body" class="anchor-body" @scroll="scrollEvent($event)">
      <slot></slot>
    </div>
  </div>
</template>
<script>
import AnchorLink from "./AnchorLink.vue";
export default {
  props: {
    float: Boolean,
    placement: {
      type: String,
      default: "leftTop",
    },
    itemHeight: {
      type: Number,
      default: 30,
    },
    data: {
      type: Array,
      default: () => [],
    },
    height: {
      type: String,
      default: "600px",
    },
  },
  components: {
    AnchorLink,
  },
  computed: {
      //获取所有锚点组合的锚点高度
    getAnchorLinkHeight() {
      let childrenHeight = 0;
      this.data.forEach((ele) => {
        if (ele.children && ele.children.length > 0) {
          childrenHeight += ele.children.length * 30;
        }
      });
      return childrenHeight + 30 * this.data.length - 20 + "px";
    },
    //获取所有锚点组合的位置
    getHeaderPosition() {
      switch (this.placement) {
        case "leftTop":
          return "placement-left-top";
        case "leftCenter":
          return "placement-left-center";
        case "leftBottom":
          return "placement-left-bottom";
        case "rightTop":
          return "placement-right-top";
        case "rightCenter":
          return "placement-right-center";
        case "rightBottom":
          return "placement-right-bottom";
        default:
          return "";
      }
    },
  },
  watch: {
      //监听数据变化,获取所有锚点(方便后面计算),获取第一个锚点的偏差
      //(因为offset计算的是当前容器被卷去的高度,可能你的高度存在偏差,所以需要减去)
    data: {
      handler(val) {
        if (val && val.length > 0) {
          this.allAnchor = this.getAllAnchor(val);
          //偏差
          this.$nextTick(() => {
            var anchor = document.querySelector(val[0].tar);
            this.offset = anchor.offsetTop;
          });
        }
      },
      deep: true,
      immediate: true,
    },
  },
  data() {
    return {
      currentAnchor: "",
      oldHeight: 0,
      scorllTimer: null,
      allAnchor: [],
      offset: 0,
    };
  },
  methods: {
      //递归获取所有锚点的值
    getAllAnchor(anchor) {
      let anchorTree = [];
      anchor.forEach((ele) => {
        anchorTree.push({ tar: ele.tar, title: ele.title });
        if (ele.children && ele.children.length > 0) {
          anchorTree.push(...this.getAllAnchor(ele.children));
        }
      });
      return anchorTree;
    },
    //点击锚点滚动
    goAnchor(selector) {
      this.currentAnchor = selector;
      var anchor = document.querySelector(selector);
      let scorllHeight = anchor.offsetTop;
      let height = this.oldHeight;
      clearInterval(this.scorllTimer);
      //使用 定时器来模拟滚动 防止一下就滚动到指定位置
      // 记得判断向上滚动还是下滚动
      //这里不得不提一个特别强大且好玩的css属性了 scroll-behavior 下面 我会解释
      if (scorllHeight > this.oldHeight) {
        this.scorllTimer = setInterval(() => {
          height = height + 50;
          if (height >= scorllHeight) {
            height = scorllHeight;
            clearInterval(this.scorllTimer);
            this.scorllTimer = null;
          }
          // 减去偏差,不然会错位
          this.$refs["anchor-body"].scrollTop = height - this.offset;
        }, 10);
      } else {
        this.scorllTimer = setInterval(() => {
          height = height - 50 - this.offset;
          if (height <= scorllHeight) {
            height = scorllHeight;
            clearInterval(this.scorllTimer);
            this.scorllTimer = null;
          }
          this.$refs["anchor-body"].scrollTop = height - this.offset;
        });
      }
      
      //锚点前面的圆圈定位
      this.allAnchor.forEach((ele, i) => {
        if (ele.tar === selector) {
          this.$refs["anchor-icon"].style.display = "block";
          this.$refs["anchor-icon"].style.top = i * 30 + 2 + "px";
        }
      });
      this.oldHeight = scorllHeight;
    },
    //页面滚动, 判断当前的scrollTop在哪个锚点之间,修改锚点的样式
    scrollEvent() {
      if (this.scorllTimer != null) {
        return;
      }
      //当前滚动位置
      let scrollTop = this.$refs["anchor-body"].scrollTop;
      this.allAnchor.forEach((ele, i) => {
        let dom = document.querySelector(ele.tar);
        if (dom) {
          if (
            scrollTop > dom.offsetTop &&
            scrollTop <= dom.offsetTop + dom.offsetHeight / 3
          ) {
            this.currentAnchor = ele.tar;
            this.$refs["anchor-icon"].style.display = "block";
            this.$refs["anchor-icon"].style.top = i * 30 + 2 + "px";
            return;
          }
        }
      });
    },
  },
};
</script>
<style lang="scss">
.cec-anchor {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-content: flex-start;
  overflow: hidden;
  .anchor-header {
    padding: 10px;
    width: 10%;
    height: auto;
    overflow: auto;
    box-sizing: border-box;
  }
  .anchor-body {
    width: 90%;
    height: 100%;
    overflow: auto;
    box-sizing: border-box;
  }
}
.cec-anchor_float {
  position: relative;
  overflow: hidden;
  .anchor-header {
    position: absolute;
    padding: 10px;
    width: 10%;
    height: auto;
    box-sizing: border-box;
  }
  .anchor-body {
    width: 100%;
    padding: 0 10%;
    height: 100%;
    overflow: auto;
    box-sizing: border-box;
  }
}
.anchor-header {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-content: flex-start;
  font-size: 14px;
  .anchor-trunk {
    margin-top: 10px;
    width: 2px;
    border-radius: 1px;
    position: relative;
    background-color: lightgrey;
    .anchor-icon {
      position: absolute;
      width: 8px;
      height: 8px;
      border-radius: 5px;
      display: none;
      left: -3px;
      background-color: #2db7f5;
      .anchor-icon-white {
        width: 4px;
        height: 4px;
        border-radius: 2px;
        margin: 2px;
        background-color: white;
      }
    }
  }
  .anchor-link {
    padding-left: 5px;
    .anchor-link-item {
      height: 30px;
      line-height: 30px;
    }
  }
}
.placement-left-top {
  top: 10px;
  left: 10px;
}
.placement-left-center {
  top: 50%;
  left: 10px;
  transform: translateY(-50%);
}
.placement-left-bottom {
  left: 10px;
  bottom: 10px;
}
.placement-right-top {
  top: 10px;
  right: 10px;
}
.placement-right-center {
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
}
.placement-right-bottom {
  bottom: 10px;
  right: 10px;
}
</style>

scroll-behavior是什么东西呢?
他是css的一个属性,但是目前这个属性的兼容性不是特别好,但是他能实现滚动条的平滑滑动,所以这里提供两种方式。
这样的话,我们就可以省略定时器滚动的方法了。

//如何使用呢?
// 我们先精简一下前面滚动的方法为
goAnchor(selector) {
      //将原来的scrollTimer变为现在的scorllClick  防止滚动条滚动多次触发
      this.scorllClick = true;
      this.currentAnchor = selector;
      var anchor = document.querySelector(selector);
      this.$refs["anchor-body"].scrollTop = anchor.offsetTop;
      this.allAnchor.forEach((ele, i) => {
        if (ele.tar === selector) {
          this.$refs["anchor-icon"].style.display = "block";
          this.$refs["anchor-icon"].style.top = i * 30 + 2 + "px";
        }
      });
      this.scorllClick = false;
    },
    scrollEvent() {
    //这里变动
      if (this.scorllClick) {
        return;
      }
      ......
      ......
      ......
    }


//最主要的是,在你需要滚动的容器中,你需要将css属性调整
.anchor-body {
    width: 90%;
    height: 100%;
    overflow: auto;
    box-sizing: border-box;

   // 这个属性会让滚动条平滑的移动
    scroll-behavior: smooth;
  }

锚点链接组件,主要是实现锚点链接的递归:

<template>
  <div>
    <div v-for="item in data" :key="item.tar">
      <div class="anchor-link-item" @click="goAnchor(item.tar)">
        <a
          :class="currentAnchor === item.tar ? 'active-a' : ''"
          href="javascript:void(0)"
        >
          {{ item.title }}
        </a>
      </div>
      <div
        v-if="item.children && item.children.length > 0"
        class="anchor-link-group"
      >
      // 递归当前组件,对应的props 及方法一定要带上,否则不生效
        <AnchorLink
          :data="item.children"
          @goAnchor="goAnchor"
          :currentAnchor="currentAnchor"
        ></AnchorLink>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "AnchorLink",
  props: {
    data: {
      type: Array,
      default: () => [],
    },
    currentAnchor: {
      type: String,
      default: "",
    },
  },
  methods: {
    goAnchor(tar) {
      this.$emit("goAnchor", tar);
    },
  },
};
</script>
<style lang="scss" scoped>
a {
  color: black;
  text-decoration: none;
}
.active-a {
  color: #2db7f5;
}
.anchor-link-group {
  margin-left: 10px;
}
</style>

在页面中使用如下:

<template>
  <Anchor :data="anchorArray" height="1000px">
    <div>
      <div id="ca">
        <p>尘埃</p>
        <pre>
有一年我们排着队奔赴衰老,扭曲着
各自斑驳的身子
时间之中,一粒粒
尘埃,恰好落在孤独的位置
 
你松弛的面孔仿佛正遗忘自己
清晨,你的目光
犹如地图上破损的线条
望着薄雾弥漫中
远飞的蝴蝶,爱会从遥远的记忆中
诞生
 
你对逝去的河流谈及你失语的喉咙
哀痛便从夏夜溢满星辰的深处流出
 
你将在这里,以一种
巨大的孤独面对自身的遗址
我走过去
你忽然向我描绘你清晨时分盛大的婚礼
 
2020.5.17
    </pre
        >
      </div>
      <div id="wt">
        <p>无题</p>
        <pre>
夏至前夕,风以遥远使我面临日暮
而心生咏叹
我那时依稀见得
落霞,在江口的淤雾中降生
 
一阵风穿过消瘦、入夜和石桥
另一阵风,穿过你
从午后走出
我便完成了下午,炽热
和两阵风之间相隔的多年哀愁
 
美呵,以至一个完整的暮色过来
你陪流水已走了多年
 
夕霞尽时
夜色空无一物,看不见来路
看不见归途
我喉间失语,如古文中再无音讯的言辞
 
2021.6.21
</pre
        >
      </div>
      <div id="y">
        <p></p>
        <pre>
——写在郑州大雨的次日夜里
 
夏日之中,你内心静得像一处深渊
暴雨,在远处轰鸣
也似至哀无声
静到极尽,你再次看到
远处雷声:白色吊灯犹如死人
 
同样是那天夜里,大雨迅速繁殖
幽暗的雨水
绵延着疼痛的子嗣
生命内外,有两种人间
我们面面相觑
——相信苦难并承认神明
 
直至某一刻,突然忆及二十年前
那时我在暴雨之中迷失
对于恐惧
对于内心执意要彻底的感受,我如何能
 
如何能
除哭泣以外的方式
毫无保留地向你尽数表达出来?
 
2021.7.23
</pre
        >
      </div>
      <div id="time">
        <p>时间</p>
        <pre>
夜深,深如一句你去年未能说完的话
像步入一条枯瘦而狭长的路
没有尽头
所以,那时的夜色
正等我望着
所以,当我望着那个地方时
会感到寂静,且遥远
 
我会时常想着
夏日应该会下几场雨,但是来不及
一一细数,秋天也要过去了
于是,我就在想着
在更久远的一场大雨中,那时
我在什么地方
 
所以,在时间之中
我望着夜色有太多遗憾。所以,我的遗憾
比隐于海底的沟壑更深,比时间
更深——
 
2020.10.24
</pre
        >
      </div>
      <div id="rq">
        <p>入秋</p>
        <pre>
入秋如被弃置,像一个没有面孔的日子
是一阵阵秋高的风
还是我内心的夜色,过于深远
 
我在床上微微侧着,想象你也在侧着
眼睛像乍醒的瞬间
突然看到他省流经此地的河流
那么遥远
那么悲伤,没有尽头
 
阳光照着我喉间的词,我心中有话
却不能说出
 
清晨,我内心有囤积了数年的尘埃
有潦草而等待风干的笔迹
有如书中等待翻过的悲痛一页:风在窗外
却不进来
 
2020.9.4
</pre
        >
      </div>
    </div>
  </Anchor>
</template>
<script>
import Anchor from "./components/anchor.vue";
export default {
  components: {
    Anchor,
  },
  data() {
    return {
      anchorArray: [
        { tar: "#ca", title: "尘埃" },
        { tar: "#wt", title: "无题" },
        { tar: "#y", title: "雨" },
        { tar: "#time", title: "时间" },
        { tar: "#rq", title: "入秋" },
      ],
    };
  },
};
</script>
<style lang="scss" scoped>
p {
  color: red;
  font-size: 20px;
  margin: 0;
  padding: 0;
}
</style>

实现效果:

在这里插入图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值