Работа с полосой прокрутки в мини-программе и реализация компонента навигации

Апплет WeChat
Работа с полосой прокрутки в мини-программе и реализация компонента навигации

Когда страница имеет длинный контент и много контента, панель навигации будет использоваться для предоставления пользователям функции легкого перехода к определенному модулю страницы. Поскольку панель навигации должна отслеживать событие прокрутки страницы, в апплете могут возникать проблемы с производительностью, и вам нужно всегда обращать внимание на количество setData в мониторе прокрутки.

В этой статье описывается функция Web Scrollbar, связанная с WeChat API, и использует эти API для реализации универсального компонента навигационного бара.

Рис. Результаты компонента навигации следующие:



API для прокрутки до целевой позиции страницы

Для достижения функции прокрутки до целевой позиции страницы требуются «операция прокрутки» и «целевая позиция».


Прокрутите страницу до нужной позиции (wx.pageScrollTo)

Чтобы прокрутить страницу до нужной позиции на странице, вы можете использовать wx.pageScrollTo API, предоставляемый WeChat. Этот метод может получать объект в качестве параметра, а объект может указывать:

  • Целевая позиция прокрутки scrollTop, единица измерения — px;

  • Время прокрутки анимации Duration, в мс;

  • selector selector, но поддерживается версия базовой библиотеки начиная с 2.7.3;

На самом деле, напрямую используя селектор, можно легко добиться желаемого эффекта, но, к сожалению, только около 60% пользователей наших апплетов имеют базовую библиотеку версии 2.7.3 или выше. Если только этим пользователям нравятся новые функции, количество пользователей немного меньше, но мы можем использовать scrollTop, чтобы напрямую указать целевую позицию.


Получить позицию элемента на странице (SelectorQuery)

Во-первых, вам нужно создать объект запроса узла selectorQuery, Метод создания выглядит следующим образом:

wx.createSelectorQuery()		// 返回selectorQuery对象

Объект selectorQuery может использовать селекторы для выбора соответствующих узлов с помощью метода selectAll:

wx.createSelectorQuery().selectAll('.nav-target') // 返回 NodesRef

NodesRef может использовать метод fields для получения информации об узле, такой как размер, набор данных и т. д., и использовать boundingClientRect для получения информации о местоположении узла, такой как координаты верхней границы и т. д., и, наконец, вызвать метод exec для выполнения:

wx.createSelectorQuery().selectAll('.nav-target').fields({
    dataset: true,	    // 指定返回节点 dataset 的信息
    size: true,		    // 指定返回节点大小信息
}, rects => {
    rects.forEach(rect => {
        rect.dataset;
        rect.width;
        rect.height;
    })
}).boundingClientRect(rects => {
    rects.forEach(rect => {
	rect.dataset;
        rect.top;
  })
}).exec()		// 最后要加 exec 才能执行


Проблемы реализации компонента панели навигации и решения

Реализация компонента панели навигации требует примерно следующих приготовлений:

  1. Получить информацию о точке привязки и сформировать копию кнопки панели навигации;

  2. Получите информацию о положении точки привязки, чтобы вы могли щелкнуть навигацию, чтобы прокрутить до соответствующей позиции;

Кроме того, необходимы две функции:

  1. Нажмите на панель навигации, прокрутите страницу до соответствующей позиции;

  2. Когда прокрутка страницы, кнопки навигации, соответствующие якору, необходимо изменить активное состояние;


Подготовка 1: получение информации о точке привязки

Мы можем согласиться с тем, что все якоря должны быть добавлены:

  • класс: навигационная цель;

  • метка данных: текст, отображаемый на панели навигации;

  • ключ данных: как идентификатор привязки;

Таким образом, элемент привязки может быть записан следующим образом:

<view class="nav-target" data-key="overview" data-label="概览">...</view>

С классом мы можем использовать selectorQuery, упомянутый выше, чтобы получить эти опорные точки, а затем использовать метод boundingClientRect, чтобы получить набор данных в опорной точке Код ключа выглядит следующим образом:

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

После получения информации о точке привязки она сохраняется в navList, где метка используется как копия кнопки панели навигации, а ключ используется для сохранения следующей позиции точки привязки.


Подготовка 2. Получите информацию о местоположении якоря

Информация о положении точки привязки также может быть получена с помощью boundingClientRect. После получения информации о положении она сохраняется на карте. Мы называем ее positionMap. В сочетании с вышеизложенным для получения информации о точке привязки код метода _getAllAnchorInfoAndScroll составляет:

_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)
    })
    
    // 为了减少setData传输数据量,我们将视图层不需要用到的position信息存在Page实例上
    res.forEach(item => {
      const { top, dataset: { key} } = item
      if (top >= 0) {
        this.positionMap[key] = Math.max(top - 55, 0)	// 向上留55px的空间给导航栏
      }
    })

    // 如果需要做滚动的操作,则在这里执行
    if (selectorIdToScroll) {
      wx.pageScrollTo({ scrollTop: this.positionMap[selectorIdToScroll] })
    }
  }).exec()
}


Динамическая загрузка модуля

Поскольку страницы, по которым нужно перемещаться, относительно длинные, мы обычно используем технологию динамической загрузки для модулей, которые не находятся выше сгиба. Динамическая загрузка модулей страницы означает, что время получения позиции привязки компонента навигации нельзя просто установить в событии готовности компонента.

Очевидно, что время получения позиции привязки должно быть установлено, когда все модули загружены. Мы можем уведомить компонент навигации об обновлении информации о привязке после загрузки модуля (компонента).


Ключевой код примерно выглядит следующим образом:

страница page.wxml

<!-- 导航组件 -->
<nav id="nav" />

<!-- 页面模块组件 -->
<page-module bindupdate="updateNavList" />

страница page.js

Page({
  updateNavList() {
     this.getNavComponent().updateNavInfo()
  },
  getNavComponent() {
    // 避免多次调用 selectComponent,将其结果存入变量 _navComponent
    if (!this._navComponent) {
      this._navComponent = this.selectComponent('#nav')
    }
    return this._navComponent
  },
})

Компонент модуля pageModule.js

// 模块组件中,加载完成时触发页面实例的 updateNavList 方法
this.triggerEvent('update')

Навигационный компонент nav.js

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


Таким образом, после обновления модуля страницы компонент навигации также обновит информацию о привязке и положение, чтобы обеспечить актуальность информации компонента навигации. Наконец, следует отметить, что при ленивой загрузке изображения высота должна быть установлена ​​заранее, иначе информация о точке привязки будет перепутана при загрузке изображения. Конечно, вы также можете вызвать метод updateNavList для обновления навигационной информации в методе при загрузке изображения.Эта часть согласуется с идеей триггера загрузки компонента модуля и не будет описана в этой статье.


Функция 1: нажмите кнопку навигации, страница прокрутится до соответствующей позиции.

С двумя предыдущими приготовлениями реализовать эту функцию намного проще. Кнопки панели навигации могут не помещаться на одной строке, а для поддержки прокрутки следует использовать тег scroll-view. Код wxml выглядит следующим образом:

Компонент навигации 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>

Среди них currentIndex записывает текущий выбранный элемент навигации; bindClickNav обрабатывает обновление currentIndex и логику прокрутки страницы выбранного элемента навигации.

Навигационный компонент nav.js

bindClickNav(e) {
  const { index, key } = e.target.dataset
  this.setData({ currentIndex: index })
  if (this.data.positionMap[selectorId] === undefined) {
    // 如果点击时,锚点位置还未取得,则需要先获取位置并传入key,在获取位置之后滚动
    this._getAllAnchorInfoAndScroll(key)
    return
  }
  wx.pageScrollTo({ scrollTop: this.positionMap[selectorId] })
},


Функция 2: состояние кнопок панели навигации поддерживает изменение при прокрутке страницы.

Функция слушателя для прокрутки страницы — это onPageScroll, где нам нужно определить, к какому якорю прокручивается страница.

Конкретная логика определения точки привязки для прокрутки реализуется с помощью watchScroll в компоненте навигации, а onPageScroll в экземпляре страницы передает позицию прокрутки страницы в метод watchScroll компонента навигации.

Страница экземпляра Page.js.

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


Как в компоненте навигации определить взаимосвязь между положением прокрутки страницы и точкой привязки?

Пример на следующем рисунке. Страница прокручивается за точки привязки «Модуль 1» и «Модуль 2», но не за точку привязки «Модуль 3». В это время «Модуль 2» отображается на панели навигации. должен быть в активном состоянии:


Подводя итоги идеи реализации: пройдитесь по каждому модулю по порядку сверху вниз, сравните положение привязки каждого модуля с scrollTop страницы и найдите последний модуль привязки меньше, чем scrollTop, и статус этого модуля активен.

Поскольку трудно найти «последний меньше чем», мы можем преобразовать его, чтобы найти модуль «первый больше чем», а предыдущий модуль модуля является активным модулем. Код ключа следующий:

Навигационный компонент nav.js

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

      // 判断是否为空,即初始化尚未完成
      if (isEmpty(this.positionMap)) {
        return
      }

      // 当页面滚动时,停止更新navIndex
      if (_navIndexLock) {
        return
      }

      // 判断滚动的scrolltop,然后设置 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

        // 寻找“第一个大于scrollTop”的模块,其上一个模块即为 active 态的模块
        if (top > pageScrollTop) {
          this.data.currentIndex !== indexToSet && this.setData({ currentIndex: indexToSet })
          break
        }

        // 到最后一个tab还没有break,说明已经滚动到了最后tab
        if (idx === lastIndex) {
          this.data.currentIndex !== lastIndex && this.setData({ currentIndex: lastIndex })
        }
      }
    }
	}
})


Суммировать

В этой статье описывается случай программы поддержки микроканалов для небольших элементов и операций прокрутки страниц, использование этих функций для достижения навигационного компонента. Компонент навигации поддерживает динамически загружаемые модули и возможность обновлять активное состояние компонента навигации в соответствии с положением прокрутки страницы.

В компоненте сложно зафиксировать время завершения динамической загрузки модуля.Здесь загруженное событие используется для запуска обновления навигации.Схему оптимизации этого метода необходимо рассмотреть и обсудить. Если у вас есть хорошие предложения, пожалуйста, оставьте сообщение для обсуждения ~


использованная литература

Официальная документация:Developers.WeChat.QQ.com/mini программа…


(Дополнительный фрагмент кодаDevelopers.WeChat.QQ.com/Yes/TK80G8MV7…)