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
Study notes, copyright Jonathan