vue music project singer page scrolling and ceiling effect



Summary singer page:

1. Obtain ['hot ', A-Z] and all singer data obtained according to ['hot', A-Z] in API

2. Render data

  • 2.1 render the left letter title ['hot', A-Z] + the singer beginning with this letter
    Because the subscript of the stored letter is consistent with that of the singer at the beginning of the corresponding letter
    ['hot', A, B, C, D, E...]
    [[[all singers of 'hot' gate], [singers beginning with letter A], [singers beginning with letter B], [singers beginning with letter C], [singers beginning with letter D]...]
    Directly circularly store the singers at the beginning of the corresponding letter and obtain the index. The corresponding letter can be obtained according to the keys of the stored letter array and the obtained index: keys[index]
  • 2.2 render the letters in the circular keys of the letter bar on the right

3. Use iscoll to wrap the content on the left so that the content beyond it can be scrolled

4. Click the letter bar on the right and scroll to the designated letter area on the left

  • 4.1 get the height (offsetTop) of the ['hot ', A-Z] title from the top and put it into the array groupsTop in order
  • 4.2 click the letter of the letter bar on the right to get the corresponding index, and get the offsetTop of the letter according to groupsTop[index]
  • 4.3 because the content on the left is wrapped by iscrol, there is a scrollTo on iscrol, which can scroll to the specified height
    this.$refs.ScrollView.scrollTo(0, -offsetY)

5. Scroll the content on the left to highlight the letter corresponding to the letter bar

  • 5.1 because the content on the left is wrapped by iscoll, there is scrolling on iscoll, which can monitor the scrolling height and obtain the current scrolling distance y (negative value)
  • 5.3 loop the array groupsTop, get groupsTop[i],groupsTop[i+1], judge groupsTop [i] < - y < groupsTop [i + 1], and the obtained I is index
  • 5.4 according to the index of the index === keys, add the class name active to highlight it

6. Ceiling effect of letter title on the left

  • 6.1 write an element (fixTitle) for storing letters, locate it at the top, and obtain the corresponding index according to the current scrolling distance y in 5
  • 6.2 keys[index] displayed in fixtitle

7. When scrolling to two titles touching, the previous title has a pushed moving effect

  • 7.1 get the height of the title fixTitleHeight(offsetHeight)
  • 7.2 offset bit of next group title + offset bit currently scrolled out: diffOffsetY = nextTop + y
  • 7.3 judge whether the calculated result is the value of 0 ~ the height of the group title 0 < diffoffsety < fixtitleheight
  • 7.4 if the above judgment is true, add this to the current fixtitle$ refs. fixTitle. style. transform = translateY(${fixTitleOffsetY}px)
  • 7.5 the judgment condition is false, fixTitleOffsetY = 0

code implementation

// 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 Navigation -->
      <!-- <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) {
      // Click the corresponding letter, get the index, and get the corresponding height from the top according to groupsTop[index]
      this.currentIndex = index
      const offsetY = this.groupsTop[index]
      this.$refs.ScrollView.scrollTo(0, -offsetY)
    },
    touchstart (e) {
      // e.target.dataset.index is a string
      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: {
    // Fixed group title
    fixTitle () {
      // this. Scroll > = 0 means to drag down when you are currently at the top
      if (this.scrollY >= 0) {
        return ''
      } else {
        // Get the current groupTitle according to the current currentIndex
        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 () {
    // Compare the offsetTop of the group by scrolling the y value to obtain the current index
    this.$refs.ScrollView.scrolling(y => {
      this.scrollY = y
      // console.log('y', y)
      // Process first area
      if (y >= 0) {
        this.currentIndex = 0
        return
      }
      // Processing intermediate areas
      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

          // When scrolling to two titles touching, the previous title has a pushed out moving effect
          // 1. Use the offset bit of the next group of titles + the offset bit currently scrolled out
          const diffOffsetY = nextTop + y
          let fixTitleOffsetY = 0
          // 2. Judge whether the calculated result is the value of 0 ~ the height of the group title
          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
        }
      }
      // Process last area
      this.currentIndex = this.groupsTop.length - 1
    })
  },
  watch: {
    // Obtain the offsetTop of each group by listening to the list
    list () {
      // Print this directly$ refs. The group is undefined. Because the data changes, the data may not be rendered
      // watch can only monitor data changes. Data changes may not have been rendered
      // In order to ensure that we can get it after rendering, we can use the $nextTick method
      // In other words, the $nextTick callback function must get the data after rendering, because$ The callback function of nextTick will not be executed until rendering is completed
      this.$nextTick(() => {
        console.log(this.$refs.group)
        this.$refs.group.forEach(group => {
          // Gets the distance from the top of all group titles
          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>

Iscoll ------ Scrollview component content

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

<script>
// Iscrol probe professional version, which can monitor the position of scrolling and other details
import IScroll from 'iscroll/build/iscroll-probe'
export default {
  name: '',
  mounted () {
    this.iscroll = new IScroll(this.$refs.wrapper, {
      mouseWheel: true,
      scrollbars: false, // Show scroll bar
      probeType: 3, //  Pixel level trigger scroll event
      // Solve the drag Caton problem
      scrollX: false,
      scrollY: true
      // disablePointer: true,
      // disableTouch: false
      // disableMouse: true
    })
    // setTimeout(() => {
    //   //The data is obtained from the network. After obtaining, the rolling range needs to be recalculated
    //   this.iscroll.refresh()
    // }, 5000)
    // 1. Create an observer object
    /**
     * MutationObserver As long as the specified content changes, the incoming callback function will be executed
     * mutationList: Changed array
     * observer: Observer object
     */
    var observer = new MutationObserver((mutationList, observer) => {
      // console.log(mutationList)
      this.iscroll.refresh()
    })
    // 2. Tell the observer what the object needs to observe
    const config = {
      childList: true, // Observe the changes of target child nodes and whether they have been added or deleted
      subtree: true, // Observe descendant nodes. The default value is false
      attributeFilter: ['height', 'offsetHeight'] // Observe the height of a specific attribute child node
    }
    // 3. Tell the observer who we need to observe and what we need to observe
    /**
     * The first parameter: tell the observer who we need to observe
     * The second parameter: tell the observer what we need to observe
     */
    observer.observe(this.$refs.wrapper, config)
  },
  methods: {
    // Provide a method to monitor the rolling distance for external use
    scrolling (Fn) {
      this.iscroll.on('scroll', function () {
        Fn(this.y) // Give the current offset bit to the outside world
      })
    },
    refresh () {
      setTimeout(() => {
        this.iscroll.refresh()
      }, 100)
    },
    // Rolling method
    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)
      })
  })
}
// Obtain the corresponding singer according to ['hot', 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)
      })
  })
}
// Obtain ['hot', A-Z] and data of all singers obtained according to ['hot', A-Z]
export const getAllArtists = letter => {
  return new Promise(function (resolve, reject) {
    const keys = ['heat']
    const list = [getHotArtists()]
    // Mr. Cheng A-Z all 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)
  })
}

Netease cloud music API

Netease cloud music API

Study notes, copyright Jonathan

Added by WildcatRudy on Mon, 03 Jan 2022 09:19:04 +0200