If the page is long and contains a lot of content, the navigation bar is used to facilitate the user to jump to a certain module on the page. Because the navigation bar needs to listen to the page scroll events, in the small program, it is easy to appear performance problems, need to always pay attention to the number of setData in the scroll listen.

This article will introduce wechat apis related to page scroll bar operations and use these apis to implement a general navigation bar component.

The navigation component effect is shown below:




Scroll to the relevant API at the destination of the page

To achieve the function of scrolling to the target location of the page, “scroll operation” and “target location” are required.


Scroll the page to the target location (wx.pagescrollto)

To scroll a page to a destination on the page, you can use the API provided by wechat, wx.pagescrollto. This method can take an object as an argument, which can be specified:

  • ScrollTop (unit: px);

  • Duration of scrolling animation, in ms;

  • Selector, but the supported base library version is from 2.7.3;

In fact, it is convenient to use the selector directly to achieve the desired effect, but unfortunately, only about 60% of our small program users base library is above 2.7.3. If these are the only users who enjoy the new feature, the number of users is slightly smaller, but we can use scrollTop to specify the target location directly.


Get the position of the element on the page (SelectorQuery)

First, we need to create the node query object selectorQuery as follows:

wx.createSelectorQuery()		// Return the selectorQuery object
Copy the code

SelectorQuery objects can select matching nodes using the selectAll method:

wx.createSelectorQuery().selectAll('.nav-target') / / return NodesRefCopy the code

NodesRef can use the fields method to obtain node information, such as size, dataset, etc., and boundingClientRect can obtain node location information, such as boundary coordinates, etc., and finally call exec method to execute:

wx.createSelectorQuery().selectAll('.nav-target').fields({
    dataset: true.// Specify to return information about the node's dataset
    size: true.// Specifies to return node size information
}, rects => {
    rects.forEach(rect= > {
        rect.dataset;
        rect.width;
        rect.height;
    })
}).boundingClientRect(rects= > {
    rects.forEach(rect= > {
	rect.dataset;
        rect.top;
  })
}).exec()		// Add an exec
Copy the code



Navigation bar component implementation problems and solutions

The implementation of the navigation bar component requires the following preparations:

  1. Obtain the information of anchor points, and compose the navigation bar button copy;

  2. Get the location information of the anchor point, so that click the navigation to scroll to the corresponding position;

In addition, two features are required:

  1. Click the navigation bar, let the page scroll to the corresponding position;

  2. When the page is scrolling, the button corresponding to the anchor point in the navigation bar needs to change the active state.


Preparation 1: Obtain anchor information

We can agree that all anchors need to add:

  • Class: nav – target;

  • Data-label: text displayed in the navigation bar;

  • Data-key: identifies the anchor point.

So, an anchor element might be written as follows:

<view class="nav-target" data-key="overview" data-label="Overview">.</view>Copy the code

The boundingClientRect method is used to retrieve the dataset from the anchor points. The key code is as follows:

wx.createSelectorQuery().selectAll('.nav-target').boundingClientRect(res= > {
	this.setData({
  	navList: res.map(item= > item.dataset).filter(Boolean)})})Copy the code

Once the anchor information is retrieved, it is stored in the navList, where the label is used as the button copy in the navigation bar, and the key is used to store the anchor location next.


Preparation 2: Obtain the location information of the anchor point

Anchor point location information, can also be obtained through boundingClientRect, take to the location information, deposited in a Map, we named positionMap, combined with anchor information above, _getAllAnchorInfoAndScroll method code is as follows:

_getAllAnchorInfoAndScroll(selectorIdToScroll) {
  wx.createSelectorQuery().selectAll('.nav-target').boundingClientRect(res= > {
    if(! res || res.length ===0) return

    this.setData({
      navList: res.map(item= > item.dataset).filter(Boolean)})// In order to reduce the amount of data transferred by setData, we store position information on the Page instance that is not needed by the view layer
    res.forEach(item= > {
      const { top, dataset: { key} } = item
      if (top >= 0) {
        this.positionMap[key] = Math.max(top - 55.0)	// Leave 55px space up for the navigation bar}})// If you need to do a scroll operation, do it here
    if (selectorIdToScroll) {
      wx.pageScrollTo({ scrollTop: this.positionMap[selectorIdToScroll] })
    }
  }).exec()
}
Copy the code


Dynamic loading of modules

Since pages that need to be navigated are long, we usually use dynamic loading techniques for modules that are not the first screen. The dynamic loading of the page module means that the timing of the navigation component getting the anchor position cannot simply be set in the component’s ready event.

Obviously, the time to get the anchor position should be set when all modules are loaded. We can notify the navigation component to update the anchor information after the module (component) is loaded.



The key code is roughly as follows:

Page page. WXML

<! -- Navigation component -->
<nav id="nav" />

<! -- Page module component -->
<page-module bindupdate="updateNavList" />
Copy the code

Page page. Js

Page({
  updateNavList() {
     this.getNavComponent().updateNavInfo()
  },
  getNavComponent() {
    // Avoid calling selectComponent multiple times and store the result in the _navComponent variable
    if (!this._navComponent) {
      this._navComponent = this.selectComponent('#nav')}return this._navComponent
  },
})
Copy the code

Module component pagemodule.js

// In the module component, the updateNavList method of the page instance is triggered when the load is complete
this.triggerEvent('update')
Copy the code

Navigation component nav.js

Component({
  methods: {
    ...,
    updateNavInfo() {
      this._getAllAnchorInfoAndScroll()
    }
  }
})
Copy the code


This way, when the page module is updated, the navigation component also updates the anchor information and location to keep the navigation component up to date. Finally, it should be noted that if there are lazy loading pictures, the height should be set in advance, otherwise the information of anchor points will be confused after loading pictures. Of course, you can also call the updateNavList method to update navigation information in the image loading method. This part is the same as the module component loading trigger idea and will not be described here.


Feature 1: Click the navigation button to scroll to the corresponding position

With the previous two preparations, this feature is much easier to implement. Navigation buttons may not fit in one row, so you should use the scrollview tag to support scrolling. The WXML code is as follows:

Navigation component nav.wxml

<scroll-view scroll-x>
  <view class="scroll-inner" bindtap="bindClickNav">
    <view class="nav {{index === currentIndex ? 'nav--active' : ''}}"
          wx:for="{{navList}}" wx:key="{{index}}" data-key="{{item.key}}" data-index="{{index}}">{{item.label}}</view>
  </view>
</scroll-view>Copy the code

CurrentIndex records the navigation item currently selected. BindClickNav handles the updated currentIndex and page scrolling logic for clicking navigation items.

Navigation component nav.js

bindClickNav(e) {
  const { index, key } = e.target.dataset
  this.setData({ currentIndex: index })
  if (this.data.positionMap[selectorId] === undefined) {
    // If the position of the anchor point is not obtained when clicked, you need to obtain the position and pass in the key, and scroll after obtaining the position
    this._getAllAnchorInfoAndScroll(key)
    return
  }
  wx.pageScrollTo({ scrollTop: this.positionMap[selectorId] })
},
Copy the code


Feature 2: The status of navigation buttons can change as the page scrolls

The listener for page scrolling is onPageScroll, where we need to determine which anchor the page is scrolling to.

The specific logic to determine which anchor to scroll to is implemented in watchScroll in the navigation component, and onPageScroll in the page instance passes the page scroll position to the navigation component watchScroll method.

Page instance page.js

Page({
  onPageScroll({ scrollTop }) {
    const navComponent = (a)= > {
      if (!this._navComponent) {
        this._navComponent = this.selectComponent('#nav')}return this._navComponent
    }
    navComponent && navComponent.watchScroll(scrollTop)
  }
})
Copy the code


In the navigation component, how do you determine the position of the page scroll in relation to the anchor point?

In the following picture, the page scrolls past the anchor points of “module 1” and “module 2”, but not past the anchor points of “module 3”. In this case, “Module 2” displayed in the navigation bar should be in active state:



To summarize the implementation idea: traverse each module from top to bottom, compare the anchor position of each module with the scrollTop of the page, find the last anchor module less than the scrollTop, and the status of the module is active.

Since “the last less than” is difficult to find, we can switch to finding “the first greater than” module, whose last module is the active module. The key codes are as follows:

Navigation component nav.js

Component({ ... .methods: {
	  ...,
    watchScroll(pageScrollTop) {

      // Check whether the initialization is not complete
      if (isEmpty(this.positionMap)) {
        return
      }

      // Stop updating navIndex while the page is scrolling
      if (_navIndexLock) {
        return
      }

      // Determine the scrolltop and set currentIndex
      const lastIndex = this.data.navList.length - 1
      for (let idx = 0; idx <= lastIndex; idx++) {
        const navItem = this.data.navList[idx]
        const top = this.positionMap[navItem.key]
        const indexToSet = idx === 0 ? idx : idx - 1

        // Find the "first module greater than scrollTop" whose last module is active
        if (top > pageScrollTop) {
          this.data.currentIndex ! == indexToSet &&this.setData({ currentIndex: indexToSet })
          break
        }

        // If the last TAB does not break, the last TAB has been rolled
        if (idx === lastIndex) {
          this.data.currentIndex ! == lastIndex &&this.setData({ currentIndex: lastIndex })
        }
      }
    }
	}
})
Copy the code


conclusion

This paper introduces the support of wechat applet for page scrolling and element operation, and uses these features to realize a navigation component. This navigation component supports dynamically loaded modules and updates the active status of the navigation component based on where the page scrolls.

Among components, it is difficult to capture the time when module dynamic loading is completed. In this case, the event after loading is used to trigger navigation update, and the optimization scheme of this method still needs to be considered and discussed. If you have good suggestions, please leave comments and discuss ~


The resources

The official document: developers.weixin.qq.com/miniprogram…

(supplement code snippet developers.weixin.qq.com/s/TK80G8mv7…).