vue音乐项目歌手页面滚动、吸顶效果

vue音乐项目歌手页面滚动、吸顶效果
vue音乐项目歌手页面滚动、吸顶效果
vue音乐项目歌手页面滚动、吸顶效果

总结singer页面:

1.api中去获取 [‘热’,A-Z] 以及根据[‘热’,A-Z]获取的所有歌手的数据

2.渲染数据

  • 2.1 渲染左边 字母title [‘热’,A-Z] + 该字母开头的歌手
    因为存储字母的下标 与 存储对应字母开头歌手的下标 是一致的
    [‘热’, A, B, C, D, E…]
    [ ['热’门的所有的歌手], [字母A开头的歌手],[字母B开头的歌手],[字母C开头的歌手],[字母D开头的歌手]…]
    直接循环存储对应字母开头歌手,获取index,根据存储字母数组的keys 以及 获取到的 index: keys[index]即可拿到对应的字母
  • 2.2 渲染右边字母条 循环keys中的字母

3.使用 iscroll 包裹左边的内容,使其超出的内容可以滚动

4.点击右边字母条,左边可以滚动到指定的字母歌手区域

  • 4.1 获取到[‘热’,A-Z]标题距离顶部的高度(offsetTop)根据顺序放进数组groupsTop
  • 4.2 点击右边的字母条的字母,可以获取到对应的index,根据 groupsTop[index] 获取该字母的offsetTop
  • 4.3 因为左侧的内容使用iscroll包裹,iscroll上有scrollTo,可以滚动到指定的高度
    this.$refs.ScrollView.scrollTo(0, -offsetY)

5.滚动左侧的内容,使字母条对应的字母高亮

  • 5.1 因为左侧的内容使用iscroll包裹,iscroll上有scrolling,可以监听滚动的高度,获取当前滚动的距离 y(负值)
  • 5.3 循环数组groupsTop,获取 groupsTop[i],groupsTop[i+1],判断 groupsTop[i]< -y < groupsTop[i+1],获取到的i就是index
  • 5.4 根据这个 index === keys的index 增加类名active使其高亮

6.左侧的字母标题吸顶效果

  • 6.1 编写一个存放字母的元素(fixTitle),定位在顶部,根据5中当前滚动的距离y获取到对应的index
  • 6.2 fixTitle中显示 keys[index]

7.当滚动到两个标题相触的时候,上一个标题有个被推出的移动效果

  • 7.1 获取到标题的高度fixTitleHeight(offsetHeight)
  • 7.2 下一组标题的偏移位 + 当前滚动出去的偏移位:diffOffsetY = nextTop + y
  • 7.3 判断计算的结果是否是0~分组标题的高度的值 0 < diffOffsetY < fixTitleHeight
  • 7.4 上面判断为true,将当前的fixTitle增加this.$refs.fixTitle.style.transform = translateY(${fixTitleOffsetY}px)
  • 7.5 判断条件为false,fixTitleOffsetY = 0
    vue音乐项目歌手页面滚动、吸顶效果

代码实现

// singer
<template>
  <div>
    <div class="singer">
      <ScrollView ref="ScrollView">
        <ul class="list-wrapper">
          <li
            class="list-group"
            v-for="(item, index) in list"
            :key="index"
            ref="group"
          >
            <h2 class="group-title">{{ keys[index] }}</h2>
            <ul>
              <li class="group-item" v-for="obj in item" :key="obj.id + index">
                <img v-lazy="obj.img1v1Url" alt="" />
                <p>{{ obj.name }}</p>
              </li>
            </ul>
          </li>
        </ul>
      </ScrollView>
      <!-- A-Z导航 -->
      <!-- <ul class="list-keys"> -->
      <!-- <li -->
      <!-- v-for="(key, index) in keys" -->
      <!-- :key="key" -->
      <!-- @click.stop="keyDown(index)" -->
      <!-- :class="{ active: currentIndex === index }" -->
      <!-- > -->
      <!-- {{ key }} -->
      <!-- </li> -->
      <!-- </ul> -->
      <ul class="list-keys">
        <li
          v-for="(key, index) in keys"
          :key="key"
          :data-index="index"
          @touchstart.stop.prevent="touchstart"
          @touchmove.stop.prevent="touchmove"
          :class="{ active: currentIndex === index }"
        >
          {{ key }}
        </li>
      </ul>
      <div class="fix-title" v-show="fixTitle !== ''" ref="fixTitle">{{fixTitle}}</div>
    </div>
  </div>
</template>

<script>
import { getAllArtists } from '../api/index'
import ScrollView from '../components/ScrollView.vue'
export default {
  name: '',
  components: {
    ScrollView
  },
  data () {
    return {
      keys: [],
      list: [],
      groupsTop: [],
      currentIndex: 0,
      beiginOffsetY: 0,
      moveOffsetY: 0,
      scrollY: 0
    }
  },
  methods: {
    _keyDown (index) {
      // 点击对应的字母,拿到index,根据groupsTop[index]获取到对应的距离顶部的高度
      this.currentIndex = index
      const offsetY = this.groupsTop[index]
      this.$refs.ScrollView.scrollTo(0, -offsetY)
    },
    touchstart (e) {
      // e.target.dataset.index 是字符串
      const index = parseInt(e.target.dataset.index)
      this._keyDown(index)

      this.beiginOffsetY = e.touches[0].pageY
    },
    touchmove (e) {
      // console.log('e', e.target)
      this.MoveOffsetY = e.touches[0].pageY
      const offsetY =
        (this.moveOffsetY - this.beiginOffsetY) / e.target.offsetHeight
      let index = parseInt(e.target.dataset.index) + Math.floor(offsetY)
      if (index < 0) {
        index = 0
      } else if (index > this.keys.length - 1) {
        index = this.keys.length - 1
      }
      this._keyDown(index)
    }
  },
  computed: {
    // 固定分组标题
    fixTitle () {
      // this.scrollY >= 0 表示当前处于最顶部的时候往下拖
      if (this.scrollY >= 0) {
        return ''
      } else {
        // 根据当前的currentIndex获取到当前的groupTitle
        return this.keys[this.currentIndex]
      }
    }
  },
  created () {
    getAllArtists()
      .then(res => {
        console.log(res)
        this.keys = res.keys
        this.list = res.list
      })
      .catch(err => {
        console.log('err', err)
      })
  },
  mounted () {
    // 通过滚动的y值 去对比 group的offsetTop,获取当前的index
    this.$refs.ScrollView.scrolling(y => {
      this.scrollY = y
      // console.log('y', y)
      // 处理第一个区域
      if (y >= 0) {
        this.currentIndex = 0
        return
      }
      // 处理中间区域
      for (let i = 0; i < this.groupsTop.length - 1; i++) {
        const preTop = this.groupsTop[i]
        const nextTop = this.groupsTop[i + 1]
        if (-y >= preTop && -y <= nextTop) {
          this.currentIndex = i

          // 当滚动到两个标题相触的时候,上一个标题有个被推出的移动效果
          // 1.用下一组标题的偏移位 + 当前滚动出去的偏移位
          const diffOffsetY = nextTop + y
          let fixTitleOffsetY = 0
          // 2.判断计算的结果是否是0~分组标题的高度的值
          if (diffOffsetY >= 0 && diffOffsetY <= this.fixTitleHeight) {
            fixTitleOffsetY = diffOffsetY - this.fixTitleHeight
          } else {
            fixTitleOffsetY = 0
          }
          if (fixTitleOffsetY === this.fixTitleOffsetY) {
            return
          }
          this.fixTitleOffsetY = fixTitleOffsetY
          this.$refs.fixTitle.style.transform = `translateY(${fixTitleOffsetY}px)`
          return
        }
      }
      // 处理最后一个区域
      this.currentIndex = this.groupsTop.length - 1
    })
  },
  watch: {
    // 通过监听list,获取每个group的offsetTop
    list () {
      // 直接打印this.$refs.group为undefined,因为数据发生变化,数据可能没有渲染完成
      // watch只能监听数据的变化,数据变化不一定已经渲染完了
      // 为了保证是渲染完成之后再去获取,我们可以借助$nextTick方法来实现
      // 也就是说在$nextTick回调函数中一定能拿到渲染完成之后的数据,因为.$nextTick的回调函数只有渲染完成之后才会执行
      this.$nextTick(() => {
        console.log(this.$refs.group)
        this.$refs.group.forEach(group => {
          // 获取所有group-title距离顶部的距离
          this.groupsTop.push(group.offsetTop)
        })
      })
    },
    fixTitle () {
      this.$nextTick(() => {
        this.fixTitleHeight = this.$refs.fixTitle.offsetHeight
      })
    }
  }
}
</script>

<style lang="scss" scoped>
@import "@/assets/css/mixin";
@import "@/assets/css/variable";
.singer {
  position: fixed;
  top: 184px;
  left: 0;
  right: 0;
  bottom: 0;
  @include bg_sub_color();
  overflow: hidden;
  .list-wrapper {
    // width: 100%;
    // height: 100%;
    .list-group {
      .group-title {
        @include bg_color();
        @include font_size($font_medium);
        color: #fff;
        padding: 10px 20px;
        box-sizing: border-box;
      }
      .group-item {
        display: flex;
        justify-content: flex-start;
        padding: 10px 20px;
        border-bottom: 1px solid #ccc;
        img {
          width: 100px;
          height: 100px;
          border-radius: 50%;
          overflow: hidden;
        }
        p {
          @include font_size($font_medium);
          @include font_color();
          display: flex;
          align-items: center;
          margin-left: 20px;
        }
      }
    }
  }
  .list-keys {
    position: fixed;
    right: 10px;
    top: 60%;
    transform: translate(-50%, -50%);
    li {
      @include font_color();
      @include font_size($font_medium_s);
      padding: 3px 0;
      &.active {
        text-shadow: 0 0 10px #000;
      }
    }
  }
  .fix-title {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    padding: 10px 20px;
    box-sizing: border-box;
    @include font_size($font_medium);
    color: #fff;
    @include bg_color()
  }
}
</style>

IScroll-----ScrollView组件内容

<template>
  <div id="wrapper" ref="wrapper">
    <slot></slot>
  </div>
</template>

<script>
// iscroll-probe专业版本,可以监听滚动的位置等细节
import IScroll from 'iscroll/build/iscroll-probe'
export default {
  name: '',
  mounted () {
    this.iscroll = new IScroll(this.$refs.wrapper, {
      mouseWheel: true,
      scrollbars: false, // 是否显示滚动条
      probeType: 3, //  像素级的触发scroll事件
      // 解决拖拽卡顿问题
      scrollX: false,
      scrollY: true
      // disablePointer: true,
      // disableTouch: false
      // disableMouse: true
    })
    // setTimeout(() => {
    //   //  数据是从网络上获取的,获取完需要重新计算滚动范围
    //   this.iscroll.refresh()
    // }, 5000)
    // 1.创建一个观察者对象
    /**
     * MutationObserver只要监听到指定内容发生变化,就会执行传入回调函数
     * mutationList:发生变化的数组
     * observer:观察者对象
     */
    var observer = new MutationObserver((mutationList, observer) => {
      // console.log(mutationList)
      this.iscroll.refresh()
    })
    // 2.告诉观察者对象需要观察什么内容
    const config = {
      childList: true, // 观察目标子节点的变化,是否有添加或者删除
      subtree: true, // 观察后代节点,默认为 false
      attributeFilter: ['height', 'offsetHeight'] // 观察特定属性 子节点的高度
    }
    // 3.告诉观察者对象,我们需要观察谁,需要观察什么内容
    /**
     * 第一个参数:告诉观察者我们需要观察谁
     * 第二个参数:告诉观察者我们需要观察什么内容
     */
    observer.observe(this.$refs.wrapper, config)
  },
  methods: {
    // 提供一个监听滚动距离的方法给外界使用
    scrolling (Fn) {
      this.iscroll.on('scroll', function () {
        Fn(this.y) // 把当前偏移位给外界
      })
    },
    refresh () {
      setTimeout(() => {
        this.iscroll.refresh()
      }, 100)
    },
    // 滚动方法
    scrollTo (x, y, time) {
      this.iscroll.scrollTo(x, y, time)
    }
  }
}
</script>

<style lang="scss" scoped>
#wrapper {
  width: 100%;
  height: 100%;
}
</style>

api

// api
export const getHotArtists = () => {
  return new Promise(function (resolve, reject) {
    Network.get('top/artists?offset=0&limit=5')
      .then(function (res) {
        resolve(res.artists)
      })
      .catch(function (err) {
        reject(err)
      })
  })
}
// 根据 ['热',A-Z] 获取对应的歌手
export const getLetterArtists = letter => {
  return new Promise(function (resolve, reject) {
    const letterArtists = []
    Network.all([
      Network.get(
        `artist/list?offset=0&limit=5&type=1&area=7&initial=${letter}`
      ),
      Network.get(
        `artist/list?offset=0&limit=5&type=1&area=96&initial=${letter}`
      ),
      Network.get(
        `artist/list?offset=0&limit=5&type=2&area=7&initial=${letter}`
      ),
      Network.get(
        `artist/list?offset=0&limit=5&type=2&area=96&initial=${letter}`
      ),
      Network.get(
        `artist/list?offset=0&limit=5&type=3&area=7&initial=${letter}`
      ),
      Network.get(
        `artist/list?offset=0&limit=5&type=3&area=96&initial=${letter}`
      )
    ])
      .then(res => {
        // console.log("res", res);
        res.forEach(item => {
          letterArtists.push(...item.artists)
        })
        // console.log('letterArtists', letterArtists)
        resolve(letterArtists)
      })
      .catch(err => {
        console.log(err)
      })
  })
}
// 获取 ['热',A-Z] 以及根据['热',A-Z]获取的所有歌手的数据
export const getAllArtists = letter => {
  return new Promise(function (resolve, reject) {
    const keys = ['热']
    const list = [getHotArtists()]
    // 先生成A-Z所有ASSCII
    for (let i = 65; i < 91; i++) {
      const char = String.fromCharCode(i)
      // console.log('cahr', char)
      keys.push(char)
      list.push(getLetterArtists(char))
    }
    Network.all(list)
      .then(res => {
        const obj = {}
        obj.keys = keys
        obj.list = res
        // console.log(res);
        resolve(obj)
      })
      .catch(err => {
        console.log(err)
        reject(err)
      })
    console.log('cahr', keys)
  })
}

网易云音乐的API

网易云音乐的API


学习笔记,版权Jonathan所有

上一篇:winform窗体全局快捷键


下一篇:CentOS7中多台服务器配置SSH免密钥登录