国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

vue3簡(jiǎn)單寫導(dǎo)航anchor示例(支持點(diǎn)擊高亮和滾動(dòng)判斷高亮)

這篇具有很好參考價(jià)值的文章主要介紹了vue3簡(jiǎn)單寫導(dǎo)航anchor示例(支持點(diǎn)擊高亮和滾動(dòng)判斷高亮)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

1.? 點(diǎn)擊anchor, 相應(yīng)的anchorlink高亮

function anchorClick(index) {
? forceStop.value = true;
? time = Date.now();
? wheelRef.value.children[index].scrollIntoView({
? ? block: 'start',
? ? behavior: 'smooth'
? });
? // 給一些延時(shí), 再點(diǎn)亮anchor, 同時(shí)不再限制scroll事件函數(shù)里面滾動(dòng)高亮的判斷
? setTimeout(() => {
? ? forceStop.value = false;
? ? time = null;
? ? currentIndex.value = index;
? }, 300 * Math.abs(currentIndex.value - index) > 1000
? ? ? 1000
? ? : 300 * Math.abs(currentIndex.value - index));
}

2. scroll頁面, 根據(jù)列表的容器高度和滾動(dòng)塊之間的數(shù)值關(guān)系判斷anchor高亮:

//滾動(dòng)的函數(shù)
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滾動(dòng)間隔時(shí)間', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetTop = navRef.value.offsetTop;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // 如果滾動(dòng)元素的scrollTop比header元素的高度+offsetTop還大, 就讓nav部分懸停在頂部!!!
  if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
    // 因?yàn)閚av懸停了, 所以scrollTop - header的高度就是判斷靠近頂部窗口的可見的list內(nèi)容了, 從而和anchorlink的高亮產(chǎn)生聯(lián)系
    const gap = scrollTop - headerOffsetHeight;
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
    isFixed.value = true;
  } else {
    isFixed.value = false;
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
}

3. 完整示例代碼:

<template>
  <div class="fixed-top-container" :ref="scrollWrapperRef">
    <header class="header" :ref="headerRef">頭部</header>

    <nav class="fixed-top-nav" :ref="navRef" :class="{ isFixed }">
      <div class="box" v-for="(item, index) in navData" :key="index">
        {{ item.title }}
      </div>
    </nav>
    <ul class="fixed-top-list" :ref="wheelRef">
      <li v-for="(item, index) in listData1">
        {{ item.name }}
        <ul>
          <li class="list-item" v-for="(item, index) in item.list">{{ item.text }}</li>
        </ul>
      </li>
    </ul>

    <ul class="anchor-conatiner">
      <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''" @click="anchorClick(index)">
        {{ item.name }}</li>
    </ul>

  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';

const isFixed = ref(false); //是否固定的
const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const wheelRef = ref('wheelRef') as Ref;
const currentIndex = ref(0);
const forceStop = ref(false);
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
let time: any = null

// mock數(shù)據(jù)-----------------------start--------------
const navData = reactive([
  { title: 'navRef', id: 1 },
  { title: 'nav2', id: 2 },
  { title: 'nav3', id: 3 },
  { title: 'nav4', id: 4 },
]);

const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sum = 0;
const listData1 = reactive(Array.from({ length: arr.length }, (item, index) => {
  const list = Array.from({ length: 5 }, (item, i) => ({
    id: 'list-item-' + i + 1,
    text: 'list-item-text-' + i,
    name: 'list-name-' + i,
  }));
  const sum1 = sum
  sum += 40 * (list.length + 1)
  return {
    listItemsHeightArrs: [sum1, sum], // 前一個(gè)標(biāo)題內(nèi)的list內(nèi)容累計(jì)高度, 當(dāng)前標(biāo)題內(nèi)的內(nèi)容累計(jì)高度
    name: arr[index] + '-累計(jì)高度為:' + JSON.stringify([sum1, sum]),
    list,
  }
}));
// mock數(shù)據(jù)-----------------------end--------------

function anchorClick(index) {
  forceStop.value = true;
  time = Date.now();
  wheelRef.value.children[index].scrollIntoView({
    block: 'start',
    behavior: 'smooth'
  });
  // 給一些延時(shí), 再點(diǎn)亮anchor, 同時(shí)不再限制scroll事件函數(shù)里面滾動(dòng)高亮的判斷
  setTimeout(() => {
    forceStop.value = false;
    time = null;
    currentIndex.value = index;
  }, 300 * Math.abs(currentIndex.value - index) > 1000
    ? 1000
    : 300 * Math.abs(currentIndex.value - index));
}


//滾動(dòng)的函數(shù)
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滾動(dòng)間隔時(shí)間', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // 如果滾動(dòng)元素的scrollTop比header元素的高度+offsetTop還大, 就讓nav部分懸停在頂部!!!
  if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
    // 因?yàn)閚av懸停了, 所以scrollTop - header的高度就是判斷靠近頂部窗口的可見的list內(nèi)容了, 從而和anchorlink的高亮產(chǎn)生聯(lián)系
    const gap = scrollTop - headerOffsetHeight;
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
    isFixed.value = true;
  } else {
    isFixed.value = false;
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
}


onMounted(() => {
  nextTick(() => {
    scrollWrapperRef.value.addEventListener('scroll', handleScroll, false);
  });
})

onBeforeUnmount(() => { // 頁面即將銷毀取消事件監(jiān)聽
  scrollWrapperRef.value.removeEventListener('scroll', handleScroll);
})
</script>
<style scoped lang="scss">
* {
  margin: 0;
  padding: 0;
}

.fixed-top-container {
  height: 100vh;
  overflow: auto;

  & .header {
    height: 200px;
    width: 100%;
    background-color: #ff5555;
  }

  & .fixed-top-nav {
    display: flex;
    width: 100%;
    background-color: #f90;

    &.isFixed {
      position: fixed;
      left: 0;
      top: 0;
      z-index: 999;
    }

    & .box {
      font-size: 14px;
      height: 30px;
      line-height: 30px;
      color: #333;
      flex: 1 1 0%;
    }
  }

  & .fixed-top-list {
    list-style: none;
    font-size: 16px;
    line-height: 40px;


    &>li {
      background-color: green;
    }

    & li {
      box-sizing: border-box;
    }

    & .list-item {
      width: 100%;
      height: 40px;
      line-height: 40px;
      font-size: 16px;
      border-bottom: 1px solid #333;
      background-color: #fff;
    }
  }

  .anchor-conatiner {
    position: fixed;
    top: 10%;
    right: 10px;

    & li {
      font-size: 14px;

      &.current {
        color: red;
      }

      &.light {
        color: green;
      }
    }
  }
}
</style>

vue3簡(jiǎn)單寫導(dǎo)航anchor示例(支持點(diǎn)擊高亮和滾動(dòng)判斷高亮),插件,vue,JavaScript面試問題,javascript,vue.js,前端

4. 如果不讓nav部分懸停:

<template>
  <div class="fixed-top-container" :ref="scrollWrapperRef">
    <header class="header" :ref="headerRef">頭部</header>

    <nav class="fixed-top-nav" :ref="navRef">
      <div class="box" v-for="(item, index) in navData" :key="index">
        {{ item.title }}
      </div>
    </nav>
    <ul class="fixed-top-list" :ref="wheelRef">
      <li v-for="(item, index) in listData1">
        {{ item.name }}
        <ul>
          <li class="list-item" v-for="(item, index) in item.list">{{ item.text }}</li>
        </ul>
      </li>
    </ul>

    <ul class="anchor-conatiner">
      <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''" @click="anchorClick(index)">
        {{ item.name }}</li>
    </ul>

  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';

const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const wheelRef = ref('wheelRef') as Ref;
const currentIndex = ref(0);
const forceStop = ref(false);
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
let time: any = null

// mock數(shù)據(jù)-----------------------start--------------
const navData = reactive([
  { title: 'navRef', id: 1 },
  { title: 'nav2', id: 2 },
  { title: 'nav3', id: 3 },
  { title: 'nav4', id: 4 },
]);

const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sum = 0;
const listData1 = reactive(Array.from({ length: arr.length }, (item, index) => {
  const list = Array.from({ length: 5 }, (item, i) => ({
    id: 'list-item-' + i + 1,
    text: 'list-item-text-' + i,
    name: 'list-name-' + i,
  }));
  const sum1 = sum
  sum += 40 * (list.length + 1)
  return {
    listItemsHeightArrs: [sum1, sum], // 前一個(gè)標(biāo)題內(nèi)的list內(nèi)容累計(jì)高度, 當(dāng)前標(biāo)題內(nèi)的內(nèi)容累計(jì)高度
    name: arr[index] + '-累計(jì)高度為:' + JSON.stringify([sum1, sum]),
    list,
  }
}));
// mock數(shù)據(jù)-----------------------end--------------

function anchorClick(index) {
  forceStop.value = true;
  time = Date.now();
  wheelRef.value.children[index].scrollIntoView({
    block: 'start',
    behavior: 'smooth'
  });
  // 給一些延時(shí), 再點(diǎn)亮anchor, 同時(shí)不再限制scroll事件函數(shù)里面滾動(dòng)高亮的判斷
  setTimeout(() => {
    forceStop.value = false;
    time = null;
    currentIndex.value = index;
  }, 300 * Math.abs(currentIndex.value - index) > 1000
    ? 1000
    : 300 * Math.abs(currentIndex.value - index));
}


//滾動(dòng)的函數(shù)
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滾動(dòng)間隔時(shí)間', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetTop = navRef.value.offsetTop;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // navOffsetTop-headerOffsetTop就是header的高度, 還需要加上nav的高度才是list內(nèi)容上面的塊的高度
  const gap = scrollTop - (navOffsetTop-headerOffsetTop+navOffsetHeight);
  if (gap >= 0) {
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
  }
  else {
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
}


onMounted(() => {
  nextTick(() => {
    scrollWrapperRef.value.addEventListener('scroll', handleScroll, false);
  });
})

onBeforeUnmount(() => { // 頁面即將銷毀取消事件監(jiān)聽
  scrollWrapperRef.value.removeEventListener('scroll', handleScroll);
})
</script>
<style scoped lang="scss">
* {
  margin: 0;
  padding: 0;
}

.fixed-top-container {
  height: 100vh;
  overflow: auto;

  & .header {
    height: 200px;
    width: 100%;
    background-color: #ff5555;
  }

  & .fixed-top-nav {
    display: flex;
    width: 100%;
    background-color: #f90;

    & .box {
      font-size: 14px;
      height: 30px;
      line-height: 30px;
      color: #333;
      flex: 1 1 0%;
    }
  }

  & .fixed-top-list {
    list-style: none;
    font-size: 16px;
    line-height: 40px;


    &>li {
      background-color: green;
    }

    & li {
      box-sizing: border-box;
    }

    & .list-item {
      width: 100%;
      height: 40px;
      line-height: 40px;
      font-size: 16px;
      border-bottom: 1px solid #333;
      background-color: #fff;
    }
  }

  .anchor-conatiner {
    position: fixed;
    top: 10%;
    right: 10px;

    & li {
      font-size: 14px;

      &.current {
        color: red;
      }

      &.light {
        color: green;
      }
    }
  }
}
</style>

5. 如果只讓list內(nèi)容區(qū)域滾動(dòng)

<template>
  <div class="fixed-top-container" :ref="scrollWrapperRef">
    <header class="header" :ref="headerRef">頭部</header>
 
    <nav class="fixed-top-nav" :ref="navRef">
      <div class="box" v-for="(item, index) in navData" :key="index">
        {{ item.title }}
      </div>
    </nav>
    <ul class="fixed-top-list" :ref="wheelRef">
      <li v-for="(item, index) in listData1">
        {{ item.name }}
        <ul>
          <li class="list-item" v-for="(item, index) in item.list">{{ item.text }}</li>
        </ul>
      </li>
    </ul>
 
    <ul class="anchor-conatiner">
      <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''" @click="anchorClick(index)">
        {{ item.name[0] }}</li>
    </ul>
 
  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';
 
const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const wheelRef = ref('wheelRef') as Ref;
const currentIndex = ref(0);
const forceStop = ref(false);
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
let time: any = null
 
// mock數(shù)據(jù)-----------------------start--------------
const navData = reactive([
  { title: 'navRef', id: 1 },
  { title: 'nav2', id: 2 },
  { title: 'nav3', id: 3 },
  { title: 'nav4', id: 4 },
]);
 
const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sum = 0;
const listData1 = reactive(Array.from({ length: arr.length }, (item, index) => {
  const list = Array.from({ length: 5 }, (item, i) => ({
    id: 'list-item-' + i + 1,
    text: 'list-item-text-' + i,
    name: 'list-name-' + i,
  }));
  const sum1 = sum
  sum += 40 * (list.length + 1)
  return {
    listItemsHeightArrs: [sum1, sum], // 前一個(gè)標(biāo)題內(nèi)的list內(nèi)容累計(jì)高度, 當(dāng)前標(biāo)題內(nèi)的內(nèi)容累計(jì)高度
    name: arr[index] + '-累計(jì)高度為:' + JSON.stringify([sum1, sum]),
    list,
  }
}));
// mock數(shù)據(jù)-----------------------end--------------
 
function anchorClick(index) {
  forceStop.value = true;
  time = Date.now();
  wheelRef.value.children[index].scrollIntoView({
    block: 'start',
    behavior: 'smooth'
  });
  // 給一些延時(shí), 再點(diǎn)亮anchor, 同時(shí)不再限制scroll事件函數(shù)里面滾動(dòng)高亮的判斷
  setTimeout(() => {
    forceStop.value = false;
    time = null;
    currentIndex.value = index;
  }, 300 * Math.abs(currentIndex.value - index) > 1000
    ? 1000
    : 300 * Math.abs(currentIndex.value - index));
}
 
 
//滾動(dòng)的函數(shù)
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滾動(dòng)間隔時(shí)間', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // 如果滾動(dòng)元素的scrollTop比header元素的高度+offsetTop還大, 就讓nav部分懸停在頂部!!!
  if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
    // 因?yàn)橹挥衛(wèi)ist內(nèi)容部分在滾動(dòng), 根據(jù)scrollTop判斷滾動(dòng)到哪個(gè)區(qū)域, 進(jìn)而判斷anchorlink的高亮
    const gap = scrollTop;
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
  }
  else {
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
  
}
 
 
onMounted(() => {
  nextTick(() => {
    wheelRef.value.addEventListener('scroll', handleScroll, false);
  });
})
 
onBeforeUnmount(() => { // 頁面即將銷毀取消事件監(jiān)聽
  wheelRef.value.removeEventListener('scroll', handleScroll);
})
</script>
<style scoped lang="scss">
* {
  margin: 0;
  padding: 0;
}
 
.fixed-top-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
 
  & .header {
    height: 200px;
    width: 100%;
    background-color: #ff5555;
  }
 
  & .fixed-top-nav {
    display: flex;
    width: 100%;
    background-color: #f90;

    & .box {
      font-size: 14px;
      height: 30px;
      line-height: 30px;
      color: #333;
      flex: 1 1 0%;
    }
  }
 
  & .fixed-top-list {
    flex: 1;
    list-style: none;
    font-size: 16px;
    line-height: 40px;
    overflow: auto;
 
    &>li {
      background-color: green;
    }
 
    & li {
      box-sizing: border-box;
    }
 
    & .list-item {
      width: 100%;
      height: 40px;
      line-height: 40px;
      font-size: 16px;
      border-bottom: 1px solid #333;
      background-color: #fff;
    }
  }
 
  .anchor-conatiner {
    position: fixed;
    top: 10%;
    right: 10px;
 
    & li {
      font-size: 14px;
 
      &.current {
        color: red;
      }
 
      &.light {
        color: green;
      }
    }
  }
}
</style>

優(yōu)化scroll判斷

之前在anchorClick事件中使用延時(shí)函數(shù), 延遲300-1000毫秒再點(diǎn)亮anchorlink, 主要是因?yàn)閟crollIntoView觸發(fā)了scroll事件, 而scroll函數(shù)中已經(jīng)有一套判斷anchorClick高亮的方法, 但那套方法有些anchorlink因?yàn)闈L動(dòng)距離的限制永遠(yuǎn)不可能點(diǎn)亮, 與anchorlink點(diǎn)擊必須高亮的原則相悖,

現(xiàn)在的需求就是要讓scroll事件函數(shù)中要照顧到每個(gè)anchorlink都可以根據(jù)滾動(dòng)距離判斷被點(diǎn)亮的情況!!!

原理:

1) 當(dāng)列表內(nèi)容大于其容器高度時(shí),在list內(nèi)容其后添加一個(gè)div標(biāo)簽, 高度就是容器高度(但注意要保留一個(gè)list-item, 否則顯得很假)

2) 即html高度 - header高度 - nav高度 - 1個(gè)list-item的高度

3) 這樣在有scroll事件觸發(fā)時(shí)anchorlink就能夠保證每個(gè)anchorlink都可能被點(diǎn)亮(之前的判斷條件下有些anchorlink是永遠(yuǎn)不可能點(diǎn)亮的, 比如'M');

<template>
    <div class="fixed-top-container" :ref="scrollWrapperRef">
        <header class="header" :ref="headerRef">頭部</header>

        <nav class="fixed-top-nav" :ref="navRef">
            <div class="box" v-for="(item, index) in navData" :key="index">
                {{ item.title }}
            </div>
        </nav>
        <ul class="fixed-top-list" :ref="wheelRef">
            <li v-for="(item, index) in listData1">
                {{ item.name }}
                <ul>
                    <li class="list-item" v-for="(item) in item.list">{{ item.text }}</li>
                </ul>
            </li>
            <!-- 列表內(nèi)容后面拼接上一段空白的文檔, 讓list內(nèi)容可以scroll到更底部去!!! -->
            <div class="rest-list-blank" v-show="restListBlankShow"></div>
        </ul>

        <ul class="anchor-conatiner">
            <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''"
                @click="anchorClick(index)">
                {{ item.name[0] }}</li>
        </ul>

    </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';

interface IProps {
    headerHeight: string;
    navHeight: string;
    itemHeight: string;
}
const props = defineProps<IProps>();
// 從父頁面?zhèn)鬟^來的數(shù)據(jù), 可以在script標(biāo)簽中使用, 也可以在style標(biāo)簽內(nèi)使用----start----
const headerHeight = props.headerHeight;
const navHeight = props.navHeight
const itemHeight = props.itemHeight;
// 從父頁面?zhèn)鬟^來的數(shù)據(jù), 可以在script標(biāo)簽中使用, 也可以在style標(biāo)簽內(nèi)使用----end----
const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const wheelRef = ref('wheelRef') as Ref;
const currentIndex = ref(0);
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
const restListBlankShow = ref(false);
let time: any = null

// mock數(shù)據(jù)-----------------------start--------------
const navData = reactive([
    { title: 'navRef', id: 1 },
    { title: 'nav2', id: 2 },
    { title: 'nav3', id: 3 },
    { title: 'nav4', id: 4 },
]);

const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sumHeight = 0;
const listData1 = reactive(Array.from({ length: arr.length }, (item, index) => {
    const list = Array.from({ length: 5 }, (item, i) => ({
        id: 'list-item-' + i + 1,
        text: 'list-item-text-' + i,
        name: 'list-name-' + i,
    }));
    const sum1 = sumHeight;
    const listItemHeight = Number(itemHeight.replace('px', ''));
    sumHeight += listItemHeight * (list.length + 1);
    return {
        listItemsHeightArrs: [sum1, sumHeight], // 前一個(gè)標(biāo)題內(nèi)的list內(nèi)容累計(jì)高度, 當(dāng)前標(biāo)題內(nèi)的內(nèi)容累計(jì)高度
        name: arr[index] + '-累計(jì)高度為:' + JSON.stringify([sum1, sumHeight]),
        list,
    }
}));
// mock數(shù)據(jù)-----------------------end--------------

function anchorClick(index) {
    time = Date.now();
    wheelRef.value.children[index].scrollIntoView({
        block: 'start',
        behavior: 'smooth'
    });
    // 給一些延時(shí), 再點(diǎn)亮anchor, 同時(shí)不再限制scroll事件函數(shù)里面滾動(dòng)高亮的判斷
    nextTick(() => {
        time = null;
        currentIndex.value = index;
    })
}


//滾動(dòng)的函數(shù)
function handleScroll(e) {
    time && console.log((Date.now() - time) / 1000, '滾動(dòng)間隔時(shí)間')
    const scrollingElement = e.target;
    const scrollTop = scrollingElement.scrollTop;
    const headerOffsetHeight = headerRef.value.offsetHeight;
    const navOffsetHeight = navRef.value.offsetHeight;
    const windowClientHeight = scrollingElement.clientHeight;
    const windowScrollHeight = scrollingElement.scrollHeight;
    const lastIdx = listData1.length - 1
    if (scrollTop >= headerOffsetHeight + navOffsetHeight) {
        // 因?yàn)橹挥衛(wèi)ist內(nèi)容部分在滾動(dòng), 根據(jù)scrollTop判斷滾動(dòng)到哪個(gè)區(qū)域, 進(jìn)而判斷anchorlink的高亮
        const gap = scrollTop;
        const idx = _.findIndex(listData1, ee => {
            const a = _.get(ee, 'listItemsHeightArrs');
            if (gap >= a[0] && gap < a[1]) {
                return ee
            }
        });
        currentIndex.value = idx;
    }
    else {
        currentIndex.value = 0;
    }
    // 滑到底部
    if (windowClientHeight + scrollTop === windowScrollHeight) {
        currentIndex.value = lastIdx;
    }

}


onMounted(() => {
    nextTick(() => {
        console.log(sumHeight, wheelRef.value.offsetHeight, 'sumHeight - wheelRef.value.offsetHeight')
        // 當(dāng)list內(nèi)容總高度大于容器高度的時(shí)候, 讓list內(nèi)容再拼接一個(gè)標(biāo)簽, 讓滾動(dòng)事件判斷的anchor高亮可以每個(gè)點(diǎn)都可能被點(diǎn)亮!!!
        restListBlankShow.value = sumHeight - wheelRef.value.offsetHeight > 0;
        wheelRef.value.addEventListener('scroll', handleScroll, false);
    });
})

onBeforeUnmount(() => { // 頁面即將銷毀取消事件監(jiān)聽
    wheelRef.value.removeEventListener('scroll', handleScroll);
})
</script>
<style scoped lang="scss">
// 使用v-bind可以獲取從props中獲取的???px的值
$header-height: v-bind(headerHeight);
$nav-height: v-bind(navHeight);
$list-item-height: v-bind(itemHeight);

// 列表內(nèi)容后面拼接上一段空白的文檔, 讓list內(nèi)容可以scroll到更底部去!!!
@mixin listItemHeight() {
    height: calc(100vh - v-bind(headerHeight) - v-bind(navHeight) - v-bind(itemHeight));
}

* {
    margin: 0;
    padding: 0;
}

.fixed-top-container {
    display: flex;
    flex-direction: column;
    height: 100vh;

    & .header {
        height: $header-height;
        width: 100%;
        background-color: #ff5555;
    }

    & .fixed-top-nav {
        display: flex;
        width: 100%;
        background-color: #f90;

        & .box {
            font-size: 14px;
            height: $nav-height;
            line-height: $nav-height;
            color: #333;
            flex: 1 1 0%;
        }
    }

    & .fixed-top-list {
        flex: 1;
        list-style: none;
        font-size: 16px;
        line-height: $list-item-height;
        overflow: auto;

        &>li {
            background-color: green;
        }

        & li {
            box-sizing: border-box;
        }

        & .list-item {
            width: 100%;
            height: $list-item-height;
            line-height: $list-item-height;
            font-size: 16px;
            border-bottom: 1px solid #333;
            background-color: #fff;
        }

        & .rest-list-blank {
            @include listItemHeight();
        }
    }

    .anchor-conatiner {
        position: fixed;
        top: 10%;
        right: 10px;

        & li {
            font-size: 14px;

            &.current {
                color: red;
            }

            &.light {
                color: green;
            }
        }
    }
}
</style>

vue3簡(jiǎn)單寫導(dǎo)航anchor示例(支持點(diǎn)擊高亮和滾動(dòng)判斷高亮),插件,vue,JavaScript面試問題,javascript,vue.js,前端

vue3簡(jiǎn)單寫導(dǎo)航anchor示例(支持點(diǎn)擊高亮和滾動(dòng)判斷高亮),插件,vue,JavaScript面試問題,javascript,vue.js,前端

父頁面使用:?

<PhoneListScroll :header-height="'200px'" :nav-height="'30px'" :item-height="'40px'"/>

注意:其實(shí)這種高度什么的在子頁面寫就行了, 這么寫不過是增加父子通信的示例罷了

6. 使用css3的transform的平移提現(xiàn)滑動(dòng)效果

6-1 做外層的標(biāo)簽定位必須是fixed(為了滑動(dòng)時(shí)頁面不抖動(dòng))

.fixed-top-container {

position: fixed;

top: 0;

left: 0;

right: 0;

bottom: 0;

overflow: hidden;

}

6-2 點(diǎn)擊achorlink時(shí)

相關(guān)anchorlink高亮, 頁面滑動(dòng)到指定位置(類似a標(biāo)簽的錨點(diǎn)效果)

function anchorClick(index) {
  const topHeight = headerOffsetHeight.value + navOffsetHeight.value;
  const sumListLength0 = index > 0 ? listData1.value[index - 1].sumListLength : 0
  wheelTransform.value = `translateY(${0 - listLineHeight.value * (index + sumListLength0) + topHeight}px)`
  wheelTransition.value = "transform 700ms cubic-bezier(0.19, 1, 0.22, 1)"
  
  nextTick(() => {
    currentIndex.value = index;
  });
}

6-3 滑動(dòng)事件

listenerTouchStart

1) 在此事件中使用finger記錄下滑動(dòng)開始手指的數(shù)據(jù)

// 開始滑動(dòng)
function listenerTouchStart(ev) {
  ev.stopPropagation();
  isInertial.value = false; // 初始狀態(tài)沒有慣性滾動(dòng)
  finger.startY = ev.targetTouches[0].pageY; // 獲取手指開始點(diǎn)擊的位置
  finger.prevMove = finger.currentMove; // 保存手指上一次的滑動(dòng)距離
  finger.startTime = Date.now(); // 保存手指開始滑動(dòng)的時(shí)間
}

listenerTouchMove

1) 使用finger記錄現(xiàn)在的手指數(shù)據(jù), 并計(jì)算現(xiàn)在和開始滑動(dòng)時(shí)2次手指位置的縱向直線距離;

2) 使用transform: translateY(xxx), 讓頁面有類似滾動(dòng)的效果

// 滑動(dòng)過程中
function listenerTouchMove(ev) {
  ev.stopPropagation();
  // startY: 開始滑動(dòng)的touch目標(biāo)的pageY: ev.targetTouches[0].pageY減去
  const nowStartY = ev.targetTouches[0].pageY; // 獲取當(dāng)前手指的位置
  // finger.startY - nowStart為此次滑動(dòng)的距離, 再加上上一次滑動(dòng)的距離finger.prevMove, 路程總長(zhǎng): (finger.startY - nowStartY) + finger.prevMove
  finger.currentMove = finger.startY - nowStartY + finger.prevMove;
  let wheelDom = _.get(wheelRef, "value");
  if (wheelDom) {
    wheelTransform.value = `translateY(${finger.currentMove}px)`;
  }
}

listenerTouchEnd

1)?使用finger記錄結(jié)束的手指數(shù)據(jù), 并計(jì)算結(jié)束時(shí)和上次的2次手指位置的縱向直線距離;

2) 讓頁面再慣性滑動(dòng)一段時(shí)間, 讓動(dòng)畫看起來不要那么死板, 因?yàn)轱@示器普遍都是60幀, 所以瀏覽器大概的刷新頻率是1000/60秒

3) 使用慣性滑動(dòng)函數(shù)inertia, 并整理inertia函數(shù)需要的參數(shù)傳入, 再使用animate這個(gè)防抖函數(shù)優(yōu)化一下

// 滑動(dòng)結(jié)束
function listenerTouchEnd(ev) {
  ev.stopPropagation();
  const _endY = ev.changedTouches[0].pageY; // 獲取結(jié)束時(shí)手指的位置
  const _entTime = Date.now(); // 獲取結(jié)束時(shí)間
  const v = (finger.startY - _endY) / (_entTime - finger.startTime); // 滾動(dòng)完畢求移動(dòng)速度 v = (s初始-s結(jié)束) / t
  const absV = Math.abs(v);
  isInertial.value = true; // 最好慣性滾動(dòng),才不會(huì)死板
  animate.start(() => inertia({ start: absV, position: Math.round(absV / v), target: 0 })); // Math.round(absV / v)=>+/-1
}

6-4 慣性滑動(dòng)函數(shù)inertia

// 慣性滑動(dòng)函數(shù)
function inertia({ start, position, target }) {
  if (start <= target || !isInertial.value) {
    animate.stop();
    finger.prevMove = finger.currentMove;
    return;
  }

  // 這段時(shí)間走的位移 S = (+/-)vt + 1/2at^2 + s1;
  const move =
    position * start * FRESH_TIME +
    0.5 * a * Math.pow(FRESH_TIME, 2) +
    finger.currentMove;
  const newStart = position * start + a * FRESH_TIME; // 根據(jù)求末速度公式: v末 = (+/-)v初 + at
  let actualMove = move; // 最后的滾動(dòng)距離
  let wheelDom = _.get(wheelRef, "value");

  // 已經(jīng)到達(dá)目標(biāo)
  // 當(dāng)滑到第一個(gè)或者最后一個(gè)picker數(shù)據(jù)的時(shí)候, 不要滑出邊界
  const minIdx = 0;
  const maxIdx = sumIndex;
  const topHeight = headerOffsetHeight.value + navOffsetHeight.value;
  const lineHeight = listLineHeight.value;
  if (Math.abs(newStart) >= Math.abs(target)) {
    if (move > topHeight) {
      // 讓滾動(dòng)在文字區(qū)域內(nèi),超出區(qū)域的滾回到邊緣的第一個(gè)文本處
      actualMove = topHeight + minIdx * lineHeight;
    } else if (Math.round((Math.abs(move) + topHeight) / lineHeight) >= maxIdx) {
      // 讓滾動(dòng)在文字區(qū)域內(nèi),超出區(qū)域的滾回到邊緣的最后一個(gè)文本處
      actualMove = position * (maxIdx - Math.ceil(topHeight / lineHeight)) * lineHeight;
    }
    if (wheelDom) wheelTransition.value =
      "transform 700ms cubic-bezier(0.19, 1, 0.22, 1)";
  }
  // finger.currentMove賦值是為了判斷anchorlink的高亮
  finger.currentMove = actualMove;
  handleScroll({ scrollTop: Math.abs(finger.currentMove - topHeight) })
  if (wheelDom) wheelTransform.value = `translateY(${finger.currentMove}px)`;

  // animate.stop();
  // animate.start(() => inertia.bind({ start: newStart, position, target }));
}

animate.js 主要是防抖, 優(yōu)化性能

export default class Animate {
  constructor() {
    this.timer = null;
  }
  start = (fn) => {
    if (!fn) {
      throw new Error('需要執(zhí)行函數(shù)');
    }
    if (this.timer) {
      this.stop();
    }
    this.timer = requestAnimationFrame(fn);
  };
  stop = () => {
    if (!this.timer) {
      return;
    }
    cancelAnimationFrame(this.timer);
    this.timer = null;
  };
}

// 或者
// function Animate () {
//   return this.timer;
// }

// Animate.prototype.start = function (fn) {
//   if (!fn) {
//     throw new Error('需要執(zhí)行函數(shù)');
//   }
//   if (this.timer) {
//     this.stop();
//   }
//   this.timer = requestAnimationFrame(fn);
// }

// Animate.prototype.stop = function () {
//   if (!this.timer) {
//     return;
//   }
//   cancelAnimationFrame(this.timer);
//   this.timer = null;
// }

// export default Animate;

6-5 滑動(dòng)真正結(jié)束(包括慣性滑動(dòng)), 判斷anchorlink高亮

function handleScroll({ scrollTop }) {
  const windowClientHeight = scrollWrapperRef.value.clientHeight;
  const windowScrollHeight = scrollWrapperRef.value.scrollHeight;
  // 如果滾動(dòng)元素的scrollTop比header元素的高度+offsetTop還大, 就讓nav部分懸停在頂部!!!
  if (scrollTop === 0) currentIndex.value = 0;
  else {
    // 因?yàn)橹挥衛(wèi)ist內(nèi)容部分在滾動(dòng), 根據(jù)scrollTop判斷滾動(dòng)到哪個(gè)區(qū)域, 進(jìn)而判斷anchorlink的高亮
    const gap = scrollTop;
    const idx = _.findIndex(listData1.value, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.value.length - 1;
  }
}

6-6 使用css3動(dòng)畫模擬scroll事件的核心代碼:

vue文件:

<template>
  <div class="fixed-top-container" :ref="scrollWrapperRef">
    <header class="header" :ref="headerRef">頭部</header>

    <nav class="fixed-top-nav" :ref="navRef">
      <div class="box" v-for="(item, index) in navData" :key="index">
        {{ item.title }}
      </div>
    </nav>
    <ul class="fixed-top-list" :ref="wheelRef" :style="{ transition: wheelTransition, transform: wheelTransform }">
      <li v-for="(item) in listData1">
        {{ item.name }}
        <ul>
          <li class="list-item" v-for="(item, index) in item.list">{{ item.text }}</li>
        </ul>
      </li>
    </ul>

    <ul class="anchor-conatiner">
      <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''" @click="anchorClick(index)">
        {{ item.name[0] }}</li>
    </ul>

  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';
import Animate from '../../utils/animate';

const a = -0.03; // 加速度
const REM_UNIT = 37.5; // 默認(rèn)375px屏幕寬度下的, html的字體大小為37.5px;
const LINE_HEIGHT = 40; // list內(nèi)容文字行高(默認(rèn)375px屏幕寬度下!);
const listLineHeight = ref(LINE_HEIGHT);
const FRESH_TIME = 1000 / 60; // 動(dòng)畫幀刷新的頻率大概是1000 / 60
const isInertial = ref(false);
const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const headerOffsetHeight = ref(0);
const navOffsetHeight = ref(0);
const wheelRef = ref('wheelRef') as Ref;
const wheelTransition = ref('');
const wheelTransform = ref('');
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
// 存儲(chǔ)手指滑動(dòng)的數(shù)據(jù)
const finger = reactive({
  startY: 0,
  startTime: 0, // 開始滑動(dòng)時(shí)間(單位:毫秒)
  currentMove: 0,
  prevMove: 0,
});

const currentIndex = ref(0);
const animate = new Animate();

// mock數(shù)據(jù)-----------------------start--------------
const navData = reactive([
  { title: 'navRef', id: 1 },
  { title: 'nav2', id: 2 },
  { title: 'nav3', id: 3 },
  { title: 'nav4', id: 4 },
]);

const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sumHeight = 0;
let sumIndex = 0;
const listData1 = ref<any[]>([]);
const getData1 = (listLineHeight) => {
  sumHeight = 0;
  sumIndex = 0;
  let sumIndex1 = 0
  return Array.from({ length: arr.length }, (item, index) => {
    const list = Array.from({ length: 5 }, (item, i) => ({
      id: 'list-item-' + i + 1,
      text: 'list-item-text-' + i,
      name: 'list-name-' + i,
    }));
    const sumHeight1 = sumHeight
    sumHeight += listLineHeight.value + listLineHeight.value * list.length;
    sumIndex += list.length + 1;
    sumIndex1 += list.length
    if (index === arr.length - 1) sumIndex -= 1;
    return {
      listItemsHeightArrs: [sumHeight1, sumHeight], // 前一個(gè)標(biāo)題內(nèi)的list內(nèi)容累計(jì)高度, 當(dāng)前標(biāo)題內(nèi)的內(nèi)容累計(jì)高度
      name: arr[index] + '-累計(jì)高度為:' + JSON.stringify([sumHeight1, sumHeight]),
      list,
      sumListLength: sumIndex1,
    }
  })
};
function initPage() {
  const currentHtmlFontSize: any = document.documentElement.style.fontSize.replace('px', '');
  // 列表li標(biāo)簽是根據(jù)css計(jì)算公式判斷的, 也可以直接通過dom得到相關(guān)的cliengtHeight數(shù)據(jù)!!!
  listLineHeight.value = Number(currentHtmlFontSize * LINE_HEIGHT/ REM_UNIT);
  listData1.value = getData1(listLineHeight);
  headerOffsetHeight.value = headerRef.value.offsetHeight;
  navOffsetHeight.value = navRef.value.offsetHeight;
}
// mock數(shù)據(jù)-----------------------end--------------

function anchorClick(index) {
  const topHeight = headerOffsetHeight.value + navOffsetHeight.value;
  const sumListLength0 = index > 0 ? listData1.value[index - 1].sumListLength : 0
  wheelTransform.value = `translateY(${0 - listLineHeight.value * (index + sumListLength0) + topHeight}px)`
  wheelTransition.value = "transform 700ms cubic-bezier(0.19, 1, 0.22, 1)"
  
  nextTick(() => {
    currentIndex.value = index;
  });
}

//滾動(dòng)的函數(shù)
function handleScroll({ scrollTop }) {
  const windowClientHeight = scrollWrapperRef.value.clientHeight;
  const windowScrollHeight = scrollWrapperRef.value.scrollHeight;
  // 如果滾動(dòng)元素的scrollTop比header元素的高度+offsetTop還大, 就讓nav部分懸停在頂部!!!
  if (scrollTop === 0) currentIndex.value = 0;
  else {
    // 因?yàn)橹挥衛(wèi)ist內(nèi)容部分在滾動(dòng), 根據(jù)scrollTop判斷滾動(dòng)到哪個(gè)區(qū)域, 進(jìn)而判斷anchorlink的高亮
    const gap = scrollTop;
    const idx = _.findIndex(listData1.value, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.value.length - 1;
  }
}

// 開始滑動(dòng)
function listenerTouchStart(ev) {
  ev.stopPropagation();
  isInertial.value = false; // 初始狀態(tài)沒有慣性滾動(dòng)
  finger.startY = ev.targetTouches[0].pageY; // 獲取手指開始點(diǎn)擊的位置
  finger.prevMove = finger.currentMove; // 保存手指上一次的滑動(dòng)距離
  finger.startTime = Date.now(); // 保存手指開始滑動(dòng)的時(shí)間
}

// 滑動(dòng)過程中
function listenerTouchMove(ev) {
  ev.stopPropagation();
  // startY: 開始滑動(dòng)的touch目標(biāo)的pageY: ev.targetTouches[0].pageY減去
  const nowStartY = ev.targetTouches[0].pageY; // 獲取當(dāng)前手指的位置
  // finger.startY - nowStart為此次滑動(dòng)的距離, 再加上上一次滑動(dòng)的距離finger.prevMove, 路程總長(zhǎng): (finger.startY - nowStartY) + finger.prevMove
  finger.currentMove = finger.startY - nowStartY + finger.prevMove;
  let wheelDom = _.get(wheelRef, "value");
  if (wheelDom) {
    wheelTransform.value = `translateY(${finger.currentMove}px)`;
  }
}

// 滑動(dòng)結(jié)束
function listenerTouchEnd(ev) {
  ev.stopPropagation();
  const _endY = ev.changedTouches[0].pageY; // 獲取結(jié)束時(shí)手指的位置
  const _entTime = Date.now(); // 獲取結(jié)束時(shí)間
  const v = (finger.startY - _endY) / (_entTime - finger.startTime); // 滾動(dòng)完畢求移動(dòng)速度 v = (s初始-s結(jié)束) / t
  const absV = Math.abs(v);
  isInertial.value = true; // 最好慣性滾動(dòng),才不會(huì)死板
  animate.start(() => inertia({ start: absV, position: Math.round(absV / v), target: 0 })); // Math.round(absV / v)=>+/-1
}

function inertia({ start, position, target }) {
  if (start <= target || !isInertial.value) {
    animate.stop();
    finger.prevMove = finger.currentMove;
    return;
  }

  // 這段時(shí)間走的位移 S = (+/-)vt + 1/2at^2 + s1;
  const move =
    position * start * FRESH_TIME +
    0.5 * a * Math.pow(FRESH_TIME, 2) +
    finger.currentMove;
  const newStart = position * start + a * FRESH_TIME; // 根據(jù)求末速度公式: v末 = (+/-)v初 + at
  let actualMove = move; // 最后的滾動(dòng)距離
  let wheelDom = _.get(wheelRef, "value");

  // 已經(jīng)到達(dá)目標(biāo)
  // 當(dāng)滑到第一個(gè)或者最后一個(gè)picker數(shù)據(jù)的時(shí)候, 不要滑出邊界
  const minIdx = 0;
  const maxIdx = sumIndex;
  const topHeight = headerOffsetHeight.value + navOffsetHeight.value;
  const lineHeight = listLineHeight.value;
  if (Math.abs(newStart) >= Math.abs(target)) {
    if (move > topHeight) {
      // 讓滾動(dòng)在文字區(qū)域內(nèi),超出區(qū)域的滾回到邊緣的第一個(gè)文本處
      actualMove = topHeight + minIdx * lineHeight;
    } else if (Math.round((Math.abs(move) + topHeight) / lineHeight) >= maxIdx) {
      // 讓滾動(dòng)在文字區(qū)域內(nèi),超出區(qū)域的滾回到邊緣的最后一個(gè)文本處
      actualMove = position * (maxIdx - Math.ceil(topHeight / lineHeight)) * lineHeight;
    }
    if (wheelDom) wheelTransition.value =
      "transform 700ms cubic-bezier(0.19, 1, 0.22, 1)";
  }
  // finger.currentMove賦值是為了判斷anchorlink的高亮
  finger.currentMove = actualMove;
  handleScroll({ scrollTop: Math.abs(finger.currentMove - topHeight) })
  if (wheelDom) wheelTransform.value = `translateY(${finger.currentMove}px)`;

  // animate.stop();
  // animate.start(() => inertia.bind({ start: newStart, position, target }));
}


onMounted(() => {
  nextTick(() => {
    initPage();
    // 綁定相關(guān)事件
    const dom = wheelRef.value
    dom.addEventListener("touchstart", listenerTouchStart, false);
    dom.addEventListener("touchmove", listenerTouchMove, false);
    dom.addEventListener("touchend", listenerTouchEnd, false);
  });
})

onBeforeUnmount(() => { // 頁面即將銷毀取消事件監(jiān)聽
  const dom = wheelRef.value
  dom.removeEventListener("touchstart", listenerTouchStart, false);
  dom.removeEventListener("touchmove", listenerTouchMove, false);
  dom.removeEventListener("touchend", listenerTouchEnd, false);
})
</script>
<style scoped lang="scss">
$pxToRemItem: 37.5px;

@function pxToRem($px) {
  $item: $pxToRemItem;
  @return $px/$item+rem;
}

* {
  margin: 0;
  padding: 0;
}

html,
body {
  overflow: hidden;
}

.fixed-top-container {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;

  & .header {
    position: relative;
    height: 200px;
    width: 100%;
    background-color: #ff5555;
    z-index: 9;
  }

  & .fixed-top-nav {
    position: relative;
    display: flex;
    width: 100%;
    background-color: #f90;
    z-index: 9;

    & .box {
      font-size: 14px;
      height: 30px;
      line-height: 30px;
      color: #333;
      flex: 1 1 0%;
    }
  }

  & .fixed-top-list {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    list-style: none;
    font-size: 16px;
    line-height: pxToRem(40px);
    z-index: 0;

    &>li {
      background-color: green;
    }

    & li {
      box-sizing: border-box;
    }

    & .list-item {
      width: 100%;
      height: pxToRem(40px);
      line-height: pxToRem(40px);
      font-size: 16px;
      border-bottom: 1px solid #333;
      background-color: #fff;
    }
  }

  .anchor-conatiner {
    position: fixed;
    top: 10%;
    right: 10px;
    z-index: 10;

    & li {
      font-size: 14px;

      &.current {
        color: red;
      }

      &.light {
        color: green;
      }
    }
  }
}
</style>

rem.js

// 設(shè)置 rem 函數(shù)
const defaultHtmlFontSize = 37.5;

export const setRem = () => {
  // 375 默認(rèn)大小37.5px; 375px = 120rem ;每個(gè)元素px基礎(chǔ)上/37.5
  const designScreenWidth = 375;

  const scale = designScreenWidth / defaultHtmlFontSize;
  const htmlWidth =
    document.documentElement.clientWidth || document.body.clientWidth;
  // 得到html的Dom元素
  const htmlDom = document.getElementsByTagName("html")[0];
  // 設(shè)置根元素字體大小
  htmlDom.style.fontSize = htmlWidth / scale + "px";
};

export const initRem = () => {
  // 初始化
  setRem();
  // 改變窗口大小時(shí)重新設(shè)置 rem
  window.onresize = function () {
    setRem();
  };
};

main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { initRem } from "./utils/rem";
import * as Vue from "vue";
import store from './store';

const app = createApp(App);
app.use(store);
app.mount("#app");

initRem()

關(guān)于css3模擬scroll效果, 還有模仿3d滾輪的文章有提到, 有興趣可以看一下:

react例子:

react寫一個(gè)簡(jiǎn)單的3d滾輪picker組件-CSDN博客

vue例子:

【精選】使用vue寫一個(gè)picker插件,使用3d滾輪的原理_vue picker-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-738039.html

到了這里,關(guān)于vue3簡(jiǎn)單寫導(dǎo)航anchor示例(支持點(diǎn)擊高亮和滾動(dòng)判斷高亮)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包