The TaroUI framework comes with an index selector, AtIndexes, but since the AtIndexes component can only accept text parameters, it cannot customize the contents. Our business requirements require customizing the contents of each item and adding multiple tags to each item. After reading the source code for the AtIndexes component and consulting TaroUI, AtIndexes is based on ScrollView, and decided to implement an index selector based on ScrollView itself.

Basic version

Implement the AtIndexes function, click the index to navigate to the list quickly.

Implementation idea:

  • To format data according to each character, for example:
[
 {
  title: 'A', items: [...]  }, { title:'B', items: [...] },... ]Copy the code
  • When formatting data, separate the alphabetic indexes and sort them. The reason for not using A fixed A-z is that there is no guarantee that there will be data under every alphabetic index.
  • Layout, alphabetic index positioned to the right of the screen viewport and centered vertically. The list on the left is alphabeticallytitleAnd useliTag loop renderingitemsEvery piece of data in

ScrollViewComponent parameter Settings:

<ScrollView
 className='scrollview'
 scrollY
 enableBackToTop scrollWithAnimation scrollIntoView={toView} scrollTop={scrollTop} style={scrollStyle} > </ScrollView>Copy the code
  • scrollY: Allows vertical scrolling.
  • enableBackToTop:iOSClick the top status bar, Android double – click the title bar, the scroll bar back to the top, only vertical support.
  • ‘scrollWithAnimation’ : Uses animation transitions when setting the scrollbar position.
  • scrollIntoView: the value should be a child elementid, sets the direction in which to scroll to the element.
  • scrollTop: Sets the vertical scroll bar position.
  • style: To use vertical scrolling, theScrollViewA fixed height.

For each index list block on the leftidclassNameSettings:

 id={`data-${item.title}`}
className={`list-items-${item.title}`}
Copy the code

Main events:

  • Click event index navigation on the right, get the navigation content of click, set the corresponding list blockid:
clickLetter(key, index) {
    const keyText = `data-${key}`
    this.setState({
      toView: keyText
    })
}
Copy the code
  • On the left side of theScrollViewComponent by settingscrollIntoView={toView}, the list scrolls to theidBlock element, to achieve the effect of fast location.

premium

The basic version just implemented simple click-to-index navigation, quick positioning effect, which was a little stiff, in the 1.5 iteration of the project, I added some effects.

1. Use the onScroll event to monitor the vertical scrolling position of the ScrollView and highlight the corresponding index on the right. 2. When the finger clicks on the right index on the screen to navigate and touchMove, the left scroll to the list block of the corresponding navigation.

Implementation ideas on the original functions:

  • Gets the height of each list block on the left (getsDOMElement highly compatible method: H5, wechat small programTaro.createSelectorQuery(), Alipay small programmy.createSelectorQuery()) :
// Get the height querySelect(nodeSelect) {const query = process.env.taro_env ==='alipay'
        ? // eslint-disable-next-line no-undef
          my.createSelectorQuery()
        : Taro.createSelectorQuery().in(this.$scope)
    return new Promise(function(resolve) {
      query
        .select(nodeSelect)
        .boundingClientRect()
        .exec(rect => {
          if (rect && rect[0]) {
            resolve(rect[0].height)
          }
        })
    })
}
Copy the code
  • Calculate the height interval for each list block:
_getListItemsHeight(stationListToJS) {const listItemsPromise = new Promise(resolve => {if (stationListToJS.length > 0) {
        let arr = []
        letListHeights = [] // Obtain all classes as.list-items- using the querySelect method${item.title}DOM height stationListTojs.foreach (item => {arr.push(this.querySelect('.list-items-${item.title}`)}) Promise. All (arr [...]). Then (res = > {/ / height range starting from 0let height = 0
          listHeights.push(height)
          for (leti = 0; i < res.length; I ++) {// Height += res[I] listheights.push (height)} resolve(listHeights)})}})return listItemsPromise
  }
Copy the code
  • incomponentDidMountCalculate the height of each index navigation on the right in the life cycle:
if (toJS(letter).length > 0) {
  this.querySelect('.letter').then(res => {
    this.setState({
      letterDOMHeight: res
    })
  })
}
Copy the code
  • incomponentDidMountLife cycle calls the method that calculates the height interval of the list block:
Const stationListToJS = toJS(stationList) const stationListToJS = toJS(stationList)if (stationListToJS.length > 0) {
  this._getListItemsHeight(stationListToJS).then(res => {
      this.setState({
        DOMHeight: [...res]
      })
  })
}
Copy the code
  • onScrollListen to the leftScrollViewThe value of the component’s vertical scrolle.detail.scrollTopTo calculate thee.detail.scrollTopFall in theDOMHeightWhich interval of the data, get the index value, set the right index navigation highlight:
// Scroll the list and highlight the corresponding letter onScroll(e) {const {DOMHeight} = this.statefor (let i = 0; i < DOMHeight.length - 1; i++) {
    let height1 = DOMHeight[i]
    let height2 = DOMHeight[i + 1]
    if(e.daile.scrollTop >= height1&&e.daile.scrollTop < height2) {// currentLetterIndex highlight this.setState({e.daile.scrollTop >= height1&&e.daile.scrollTop < Height2) { currentLetterIndex: i })return}}}Copy the code
  • Add index navigation on the righttouchStart,touchMoveEvent that listens and calculates how far and how many index values have been moved on the index navigation.
  • touchStartEvents:
TouchStartLetter (e) {touchStartLetter(e) {touchStartLetter(e) {touchStartLetter(e)${item}-${index}`}
  const targetArr = e.target.id.split(The '-')
  const letterText = targetArr[1]
  const index = targetArr[2]
  const keyText = `data-${letterText}'// e.touches[0]. PageY where the record starts sliding // startIndex touchStart index touched by the event // currentLetterIndex Index of the current index navigation this.setState({ toView: keyText, currentLetterIndex: index, startIndex: index, touchStartPageY: e.touches[0].pageY }) }Copy the code
  • touchMoveEvents:
TouchMoveLetter (e) {e.topPropagation () e.preventDefault() const {touchStartPageY, LetterDOMHeight, startIndex, DOMHeight} = this.state const letter = toJS(this.props. Letter) Calculate how many indexes slideletThe diff = ((e. ouches. [0] pageY - touchStartPageY)/letterDOMHeight) | 0 / slide/processing to the top and the bottom boundary value / / eslint - disable - next - line radixlet curIndex = parseInt(startIndex) + diff
  if (curIndex < 0) {
    curIndex = 0
  } else if(curIndex > domheight.length-2) {curIndex = domheight.length-2let keyText = `data-`
  letter.forEach((item, index) => {
    if (curIndex === index) {
      keyText = keyText + item
    }
  })
  this.setState({
    toView: keyText,
    currentLetterIndex: curIndex
  })
}
Copy the code

DOM element SettingsidclassName

  • Right index navigation set properties:
<View className='letter-wrapper'>
  {letter.length > 0 &&
    letter.map((item, index) => {
      return (
        <View
          className={
            index === Number(currentLetterIndex) ? 'letter active' : 'letter'
          }
          key={item}
          id={`letter-${item}-${index}`}
          onClick={this.clickLetter.bind(this, item, index)}
          onTouchStart={this.touchStartLetter}
          onTouchMove={this.touchMoveLetter}
        >
          {item}
        </View>
      )
    })}
</View>
Copy the code
  • Left list block DOM setting properties:
<View
  key={item.title + index}
  className={`list-items list-items-${item.title}`}
  id={`data-${item.title}`} >... < / View >Copy the code

END

Note the solution to the index selector and the calculation of some values.