# 开发 \| 餐饮小程序必备！教你轻松做出像「饿了么」一样的点餐界面

- 作者：知晓程序
- 发布时间：2017-11-29 19:00
- 原文：https://www.ifanr.com/minapp/947043

![](https://s3.ifanr.com/wp-content/uploads/2017/07/minapp-dev-final-feature.jpg)

文 \| zyh2668

**知晓程序注：**

许多购物、外卖小程序，都会做「分栏」设计，即在左侧展示商品分类、右侧展示分类下的具体商品。

如何将分类栏固定在屏幕上呢？使用 sitcky 特性，或许是个方案。

今天，知晓程序就来为大家讲解，如何在小程序中使用 sticky 的方法，将页面元素固定在屏幕上。

关注「知晓程序」微信公众号，回复「开发」，获取小程序优化秘籍。

---

### 什么是 sticky 效果？

简单地说，**sticky 就是标题栏的「粘粘」效果**，向下滑动时跟着列表走、向上滑动到顶部时将会固定在顶部。

![](https://s3.ifanr.com/wp-content/uploads/2017/11/aaa-6.jpg)

顶部的蓝色条幅，就是 sticky 后的效果

如果不考虑不同浏览器兼容性，CSS 3 就有一个 `position: sticky` 属性，就能实现这种效果。

```
{
  position: sticky;
  top: 0;
}
```

只需要这两行就能实现，然而…… 在不同浏览器中，这个属性的兼容性，那是相当的差。

### 在小程序里，如何实现固定效果？

在小程序里实现 sticky 效果，**我们需要利用小程序 `scroll-view` 组件里的 `scroll-into-view` 属性**。

首先，我们需要获取每个 `scroll-into-view` 的 `scrollTop`，并且监听 `scroll` 的滚动，并改变 `scroll-into-view` 的值。

下面，来让我们看一下具体该如何实现。

```
<scroll-view scroll-y class="left-wrapper" id="left">
  <view wx:for="..." bindtap="..."></view>
  <!--这里是左侧的类型选择-->
</scroll-view>
<scroll-view scroll-y
 class="right-wrapper"
 bindscroll="onScroll"
 scroll-into-view="{{toView}}"
 id="right">
  <view wx:for="{{items}}" wx-for-item="item" class="lists" id="{{item.title}}">
    <view class="type-title" style="{{style}}">
      <!-- 这个就是ticky header部分 -->
      {{item.title}}
    </view>
    <view class="content">
      <view wx:for="{{item.child}}" class="item">
        <!--这里是需要展示具体的列表项-->
      </view>
    </view>
  </view>
</scroll-view>
```

左侧列表页没什么好讲的，无非就是按下某个类型，给上一个 `checked` 样式，然后改变 `toView` 的值。

那么 `toView` 是什么呢？**首先，`toView` 的值是和 `scroll-view` 里面你需要跳转的 `view` 的 `id` 对应起来的**，也就是以下代码中的 `id`。

```
<view wx:for="{{items}}" wx-for-item="item" class="lists" id="{{item.title}}">
```

当用户按下左侧对应的按钮，右侧的 `scroll` 就会跳转到相应 `id` 的 `scroll-into-view` 里面。

到目前为止，我们已经实现了sticky header + 跳转的问题了。**但如果滑动右侧的滚动条的话，左侧的数据如何跟着变化呢？**

假如不是小程序的话，应该很多人都知道怎么做——无非就是监听滚动条，判断滚动条的位置，然后根据区域去改变左侧的选择。但是，**小程序如何获得 `scroll-into-view` 在 `scroll-view` 里面的位置呢？**

众所周知，小程序是没有类似 `document.getElementById()` 这种 DOM 操作的，也没法使用 jQuery 的 `$` 对象，快捷获取 `scrollTop`，还不能像 vue 一样，直接操作 `$el`。

还好，小程序基础库 1.4.x 开放了一个接口：`wx.createSelectorQuery()`。

使用这个接口，小程序将会返回一个 `SelectorQuery` 对象实例。**可以在这个实例上使用 `select` 等方法选择节点，并使用 `boundingClientRect` 等方法选择需要查询的信息。**

```
nodesRef.boundingClientRect([callback])
```

添加节点的布局位置的查询请求，相对于显示区域，以像素为单位。其功能类似于 DOM 的 `getBoundingClientRect`。返回值是 `nodesRef` 对应的 `selectorQuery`。

返回的节点信息中，每个节点的位置用 `left`、`right`、`top`、`bottom`、`width`、`height` 字段描述。如果提供了 `callback` 回调函数，在执行 `selectQuery` 的 `exec` 方法后，节点信息会在 `callback` 中返回。

然后，**我们可以通过这个方法拿到所有的 `scroll-into-view` 的位置。**

```
let query = wepy.createSelectorQuery()
for (let i = 0; i < this.types.length; ++i) {
  let id = this.types[i]
  query.select(`#${id}`).boundingClientRect((rect) => {
    this.scrollTops[id] = rect.top
  }).exec()
}
```

需要注意的是，这个操作必须得放在 `onReady()` 的时候去做，否则将无法得到 `rect` 属性。

得到这个属性以后其实就很好操作了，直接上代码：

```
onScroll (event) {
  // 如果是右侧的滚动view
  if (event.currentTarget.id === 'right') {
    // 判断滚动的方向
    let top = event.detail.scrollTop
    this.dir = this.currentTop < top ? 'down' : 'up'
    this.currentTop = top
    // 判断当前滚动条所在区域，如果不在当前区域则进行跳转
    if (top > this.scrollTops[this.getNextView()] &&
     this.dir === 'down' &&
     this.checked < this.types.length - 1) {
      this.setChecked(this.checked + 1)
    }
    if (top < this.scrollTops[this.toView] &&
     this.dir === 'up' &&
     this.checked > 0) {
      this.setChecked(this.checked - 1)
    }
  }
}
```

一个简单的、具有 sticky 效果的商品列表页，以及分类跳转功能，就实现了。

### 坑与问题

首先，**`scroll-view` 必须设置高度**，否则在 iOS 上，将无法使用 `scroll-into-view` 跳转。另外，**页面渲染完成后，才能使用 `createSelectorQuery`**。

此外，`scroll` 会有个惯性运动。这时候，按左侧的按钮切换 `scroll-into-view` 会和 `onScroll` 事件发生一些冲突，实测在 iOS 存在有该问题，希望大神给予些指导意见。

### 最后的话

由于采用了 wepy 构建的小程序，所以在部分代码上会有出入或相似的地方。但我们主要学习的是思路。

wepy 的本意是希望小程序能像 vue 一样开发，由于本人一直在用vue做项目，所以用 wepy 开发小程序会顺手一些。

但是 wepy 虽然尽力贴合 vue，但在某些设计上存在着一定的问题。不过，使用 wepy 已经比直接开发小程序用起来舒服一些。

**关注「知晓程序」公众号，在微信后台回复「开发」，让你的小程序性能再上一层楼。**

**![](https://s3.ifanr.com/wp-content/uploads/2017/11/20171101173458.jpg)**

![](https://s3.ifanr.com/wp-content/uploads/2017/09/bottomqrcode.gif)
