【踩坑】微信端vue+vant+better-scroll ,目前基本填平欢迎讨论~

前言

本篇文章为基于better-scroll的 vue 移动端项目踩坑记录,主要围绕better-scroll这个插件在项目中所带来的问题进行记录。javascript

固然最后还会有部分章节为其余插件踩坑和一下技巧运用的内容。css

详情能够参考下方目录。html

导航





为何使用 better-scroll

其实一开始的核心是为了解决:ios

微信内置浏览器下拉显示网页信息这个特色,所致使页面动效错乱git

的这个问题。github

如图所示,微信浏览器还真的是奇奇怪怪(还有好比说 ios 的底部导航条)。因而我就很好奇为何会有一个默认的下拉行为,他的意义是什么,我就马上去网上查了查。
然而根据查询的结果好像并没获得详细合理的解释,大概就是由于微信浏览器是内嵌 webview,而 webview 在小程序应用中须要有这个下拉刷新的默认行为,虽然微信浏览器不须要下拉刷新,但仍然是保留了这个机制。。。
Emm...好吧! 也许还有更加合理的理由来解释保留这个机制的缘由,尽管我尚未找到,但项目要继续完成,因而开始着手解决。
固然要解决这个问题,直接去禁用页面的 touchmove 事件显然不是一个稳妥的方案,因此...
web


搜索了一番,便找到了一款强大的移动端滚动解决方案,better-scroll 这个插件。老实说一开始上手使用仍是有点磕磕绊绊的,多是我没有认真读文档的前导。。anyway,后来随着项目的进行,边用边读文档,愈加感到这个插件的功能之强大,实在是很是好用了。




better-scroll 概要

介绍

其实早在 bs(简称)以前就了解到 iscroll 滚动库,但其已经中止维护。而 better-scroll 是基于 iscroll 重写的一款解决移动端滚动场景的插件,api 兼容 iscroll,另作了优化。体积小巧功能强大。


滚动原理

先放一张官网上的图,会更加直观清晰。

滚动原理大体能够理解为wrapper是父容器,content为子容器。当子容器的高度超过了父容器,就能够开启滚动。

这个滚动原理很是重要,我就是事先没有特别注意这里的前导,致使我直接使用的时候发现怎么不管滚动也没有效果,再倒回来看。

具体的底层原理是基于requestAnimationFrame这个 api 来实现动画,不深刻阐述。




具体踩坑

初始化完成可是没法滚动

<div class="wrapper" ref="wrapper">
  <div class="content">
    <router-view />
  </div>
</div>
复制代码
import Bscroll from "@better-scroll/core";

export default {
  mounted() {
    let scroll = new BScroll(this.$refs.wrapper, {});
  },
};
复制代码

初始化后没法滚动会有不少缘由,首先很是重要的一点是:实例化的时机。

1. BScroll的实例化必定要放在$nextTick的回调函数中。

mounted() {
  this.$nextTick(() => {
    let scroll = new BScroll(this.$refs.wrapper, {})
  })
}
复制代码

这是由于咱们要在 DOM 已经渲染完毕后,才能确保获取到须要添加滚动的元素,以及滚动父元素和子元素的高度,来计算是否要添加滚动。


2. DOM 层级不对

contentdom 必须是wrapper第一子元素。这是官网使用方法给出的规定,遵照便可。


3. 父元素没有设置固定的高度 or 子元素高度没有超过父元素

这一点很好理解,很简单也很重要。这也就是为何没有读前导的我发现滚动无效的根本缘由。

必定要给父元素固定的高度,由于一般父元素的高度会由子元素撑开,而滚动开启的条件是当子元素的高度超出父元素,因此父元素的固定高度是关键。

.wrapper {
  weight: 100vw;
  height: 100vh;
}
复制代码

此时当子元素content的内容高度超出一屏时,天然就可以滚动,而 wrapper 之外的任何元素都不会滚动。

那么至此,不管页面是否能够滚动,微信内置浏览器的下拉行为已经阻止,然而踩坑还在继续。



配合 vant 的 Tabs 组件时,切换后 tab 页面滚动出错

理解了前导的滚动原理,那么这个问题也基本上不算是问题。

其实一开始看到个人 tab 页的表现是懵的,心想我好好的 tab 页滚动怎么出了问题,烦躁。。

冷静想想也很简单,无非就是切换 tab 页面的时候动态地改变了子元素的高度,然而插件人家并不知道啊。刚进页面的时候,插件获取了页面的高度,此时一切都正常。tab 页就是对几个 div 进行 display 的切换,切换 tab 页改变了内容高度,那么让插件从新获取一次便可。 寻找 api ->

ps:若是是 layout 级别的应用,记得把实例化的 scroll 存储在 store 里面。

export default {
  computed: {
    ...mapState({
      scroll: (state) => state.layout.scroll,
    }),
  },
  methods: {
    tabChange() {
      this.$nextTick(() => {
        this.scroll.refresh();
      });
    },
  },
};
复制代码

这里一样须要在$nextTick的回调中调用refresh,理由一模一样。



切换 tab 页滚动至顶部,偶现失效

整个项目我一共有三个页面使用到了 Tabs 组件,然而在初期制做第一个 Tab 页面的时候,这个问题没有出现过。可是当我全部页面所有制做完成后,第一个制做的 Tab 页面开始做妖了。。。

具体表现为切换 tab 页后,没法滚动至顶部,而且预设动效时间变的缓慢,好像被中途中止了同样。

export default {
  methods: {
    tabChange() {
      this.scroll.scrollTo(0, 0, 500); // 参数:x坐标 y坐标 动效时间 动画函数

      this.$nextTick(() => {
        this.scroll.refresh();
      });
    },
  },
};
复制代码

我思考滚动至顶部这个动做和计算页面高度并无任何关联,那么滚动的动做理论上来讲能够在scroll.refresh()以前执行,没毛病。

尽管如此,我屡次尝试改变scrollTorefresh的执行顺序依旧没有效果。但有一点很神奇的是,别的 tab 页面没有出现 bug,另外若是我删除动效时间这个参数,那么 bug 会消失。

this.scroll.scrollTo(0, 0); // 没有出现bug
复制代码

我思来想去,期间我只作过一件事情,那就是修改了实例化better-scroll时的配置选项:

{
  useTransition: false, // 我添加了这一条属性
}
复制代码

然而我其实是必须须要这条配置的,后面的章节会提到。这个选项大体是将页面滚动的效果从 css 的 transition 切换到了 js 来执行,本意是以默认的 css 配置来优化页面滚动的性能。

我猜测,也许是修改配置后执行的scrollTo方法和refresh方法有冲突,滚动被refresh中断等等之类瞎猜的缘由,因此我将目标放在了:如何让scrollTo方法在refresh后执行。

tabChange() {
  this.$nextTick(() => {
    this.scorll.refresh();
    this.scorll.scrollTo(0, 0, 500); // 无效
  });
};
复制代码
tabChange() {
  this.$nextTick(async () => {
    this.scorll.refresh();
    await this.scorll.scrollTo(0, 0, 500); // 无效
  });
};
复制代码

最后,我想起来曾经看到过

$nextTick()方法不传回调函数返回一个 promise

相似的语句,猜测$nextTick 方法挂起的回调函数应该是一个micro task,那么我建立一个macro taskscrollTo方法在$nextTick的回调函数结束后在执行,是否会成功呢。

tabChange() {
  setTimeout(() => {
    this.scroll.scrollTo(0, 0, 500)
  }, 0)

  this.$nextTick(() => {
    this.scroll.refresh()
  })
}
复制代码

结果是成功了

最终虽然解决了问题,可是目前仍然没有探究明白是什么缘由致使scrollTo方法的失效,以及个人猜测是否正确。

从思考的角度来讲,切换 tab 页面,应当先执行refresh函数来从新定义BS实例所须要的页面信息,而后再执行scrollTo函数,让页面滚动到合适的位置。产生该 bug 最大的可能性应该是,因为执行顺序不正确,页面正在处于滚动状态的时候触发refresh函数才使得滚动失效。

是否有更加优雅的解决方案,还有对 bug 的探究等往后补充了。



在 ios 设备上,滚动期间触屏会致使页面高度抖动

在项目前期我根本没有注意到这个问题,直到我总览整个项目的各个页面的时候,发现这个页面滚动的效果不理想,老是以为很奇怪,但又说不上来。

因而退出到手机 home 界面打开了设置,开始滑动。那个流畅的感受...再切回来滑动就很快发现了问题 —— 人们滑动页面的时候并不会等上一次滑动动画结束后才进行下一次的滑动,一般会进行连续的点击和滑动,此时页面再安卓端没有任何问题。可是在 ios 上面,则变现为滑动期间触屏会使页面滚动高度抖动一次。连续的滑动就会形成连续的抖动,这个问题就很严重了。。。

查询了官网的 issue,给出了以下建议:

{
  useTransition: true; // 使用该项配置
}
复制代码

行吧。。。全用 css 毕竟还使有缺陷的,我们不能要了性能丢了体验,因而滑动流畅多了。

这个问题解决起来仍是比较简单,关键是在于如何查找问题。简单的 google 或者 baidu 有时并不会给你满意的答案,这个时候 官网的 issue 或者一些国外的平台(stack overflow) 也许会十分有帮助,且高效。



实例化时基本的经常使用配置

import BScroll from "@better-scroll/core";

export default {
  mounted() {
    this.$nextTick(() => {
      let scroll = new BScroll(this.$refs.wrapper, {
        // 发现页面触发不了点击事件加上他们
        click: true,
        tap: true,

        // * 触发滚动事件的类型,3为实时触发
        probeType: 3,

        // 容许y方向的滚动
        scrollY: true,

        // 页面滚动到两端时的过量回弹效果,默认是 true
        bounce: false,

        // 使用js实现滚动
        useTransition: false,

        // * 容许触发默认事件的标签名正则
        preventDefaultException: {
          tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO|VIDEO|IMG)$/,
        },
      });
    });
  },
};
复制代码
  • 这里重点说两个地方,一是probeType

这个属性,简单来讲就是触发监听事件的频率。在项目中我有需求去实时获取滑屏的方向,来制做顶部导航 slideDown 的效果。那么一开始我发现监听scroll事件没有任何反应,我还觉得是 api 更新了。看了 issue 才发现当probeType值为1的时候scroll事件是不会触发的。。。

那么具体所对应的值有什么效果,能够去 api 文档去查看,很少赘述。但一般来讲,想要完成项目复杂的需求,大多数状况下仍是须要probeType3

  • 另外一个就是preventDefaultException

字面意思,这是个正则表达式。在配置中有一个preventDefault属性默认值是true。该属性禁用了大部分的默认行为,官方也不建议修改此选项。可是咱们能够经过preventDefaultException属性来恢复部分标签的默认行为。这一点也是比较重要的,官方默认值为

{ tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/, }
复制代码

这些。因为项目里面我使用到了 imgvideo 标签。图片须要长按识别,而视频的默认控件须要默认行为的支持,因此直接将标签名放入这个正则表达式便可。




项目其余踩坑 or 技术

活用 less 特性,重用自定义样式的 vant 组件

一般项目中会有些奇奇怪怪的设计,现有的 ui 库拿来无法直接用。例如个人项目中标签页的设计,他们说叫沉浸式。

正常的标签页是这样子的:

标题 | 标签页 | 内容

三个区域互不干扰,却又有联动。

而咱们的设计是这样的:

没错,在某一个标签的展现中: 内容的一半 | 标签页 | 标题 拥有一个沉浸式地背景

这样一来只能从新设计整个标签页的结构,那么新的结构必须是这样的:

如此,就能够自定义整块标题+内容区域的样式。

通过一点点的修改和尝试,终于完成了对样式的修改。(同时还要注意标签底部的条动效位置要正确),代码以下:

/deep/ .van-tabs__wrap {
  position: absolute;
  width: 90vw;
  height: 42px;
  top: 100px + 48px;
  transform: translate(5vw);
}
/deep/ .van-hairline--top-bottom::after {
  border: 0;
  border-bottom: 1px solid rgba(51, 51, 51, 0.1);
}
/deep/ .van-tabs__nav {
  justify-content: space-between;
}
/deep/ .van-tab {
  width: 60px;
  flex: 0;
  flex-basis: 70px;
}
复制代码

那么重点来了,依此法修改的组件样式,最好应用于项目中全部的标签页,以防止之后其余页面出现相同需求。那么这一串样式代码在每一个组件中去复制显然是比较冗余的。

能够巧妙的运用 less 封装的能力将这一串代码放入命名空间。

// global.less

#custom-vant {
  .absolute_tab {
    /deep/ .van-tabs__wrap {
      position: absolute;
      width: 90vw;
      height: 42px;
      top: 100px + 48px;
      transform: translate(5vw);
    }
    /deep/ .van-hairline--top-bottom::after {
      border: 0;
      border-bottom: 1px solid rgba(51, 51, 51, 0.1);
    }
    /deep/ .van-tabs__nav {
      justify-content: space-between;
    }
    /deep/ .van-tab {
      width: 60px;
      flex: 0;
      flex-basis: 70px;
    }
  }
}
复制代码

那么在使用的时候,样式代码会很是地简洁:

@import url("../../assets/css/global.less");

.tabs {
  #custom-vant.absolute_tab();
}
复制代码

记得运用的时候要引入全局 less 文件。一样的方法,能够定义全局变量,例如主题颜色等。

// global.less
#theme_color {
  @purple: rgba(73, 56, 141, 1);
  @gray: rgba(153, 153, 153, 1);
  // ...
}
复制代码
@import url("../../assets/css/global.less");
.box {
  background: #theme_color[ @purple ];
}
复制代码

关于 less 的使用,文档上面已经很是的详细了,那么更多的使用技巧欢迎你们一块儿来讨论学习。



SwiperJs 来定制轮播图, loop 属性致使图片加载失败

swiper 的轮播图的确很是强大,可以实现的效果然的很是多。可是文档真的不是很好用。。。

最快的方法应该是翻官网上的示例的代码,直接拿过来用。但就这样也不必定就能成功,况且设计可能还有些出入。总之项目有一块轮播的需求是这样的:

咱们的需求和官网的例子还不是彻底同样,由于他先后两个 banner 图并非彻底显示的,有一半藏在外面。这倒也好办,直接把 div 拉出屏幕再居中就行了。

问题来了,轮播图须要无限循环。当我加入 loop 这个属性后,第一张图的前 n 张图加载失败了。

也就是只能看见传入的四张图片。我本来觉得是由于引入方式的关系,我将图片地址换为静态路径,线上地址都没有用。

<div class="swiper-container">
  <div class="swiper-wrapper">
    <div class="swiper-slide" v-for="(item, index) in swiperList" :key="index">
      <VanImage class="swiper-img" :src="..." fit="cover" />
    </div>
  </div>
</div>
复制代码
export default {
  mounted() {
    this.$nextTick(() => {
      new Swiper(".swiper-container", {
        slidesPerView: 3, // 能够看见3张banner图
        spaceBetween: 8, // 图片之间的间隙
        centeredSlides: true, // 是否居中显示
        loop: true, // 循环显示
      });
    });
  },
};
复制代码

最后我发现了一下奇怪的细节,没法显示的图片,显示的 vant 组件 Image 的默认图片,而图片有一丝很是细的边框,就好像底下有图片,被默认 alt 属性中的图片遮盖住了。这很神奇。

后来我弃用了 vant 的组件,改为原生的img组件,bug 就解决了。也许是由于实现无限循环,重复的图片是拷贝出来的 html 片断,没有 js 代码,而 Vant 组件并无接受到图片的地址,因此默认贴上了alt属性的图片。才致使看起来图片加载失败了。

看来 不一样的组件组合使用的时候,极可能会出现不兼容的状况。 因此在选择使用的时候仍是须要当心一些。


顺带提一句,轮播图里item的样式也是要本身写的。。直接去看官网上面的例子,样式直接拿来用便可。

// 周边的banner图片进行缩放0.8,是在css中写出来的
.swiper-slide {
  // ....

  /* Center slide text vertically */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transition: 300ms;
  transform: scale(0.8);
}
.swiper-slide-active,
.swiper-slide-duplicate-active {
  transform: scale(1);
}
复制代码

另外在 vue 项目中使用 swiper 的时候,须要手动引入 swiper 的样式,这也是一个样式没法生效的常见缘由。

// less
@import url("../../../node_modules/swiper/css/swiper.min.css");
复制代码



微信中使用原生 Video 标签的一些问题

最根本的问题其实就是 andiordios 端微信浏览器的内核不同所致使的一系列恶心的问题。

  • 安卓端微信浏览器内核为 X5
  • ios 端微信浏览器内核是 chrome 内核

ios 端表现其实十分良好,重点就在于安卓端的兼容处理。

先重中之重!千万不要加这条属性:

<video x5-video-player-type="h5" />
复制代码

千万不要!这是个大坑,不要尝试在这条属性基础上进行兼容。这条属性意味声明启用同层 h5 播放器。

若是你们有基于同层 h5 播放器好的兼容方案,能够讨论一下。

其他就是一些实现行内播放的兼容属性,经常使用的属性大体为以下代码

<video preload="auto" x5-video-orientation="portraint" x5-video-player-fullscreen="true" x5-playsinline="" playsinline="" webkit-playsinline="" x-webkit-airplay controls="controls" class="video" ref="video" src="https://hxy-web1.oss-cn-hangzhou.aliyuncs.com/meiye/wuqihua_vlog_v5.mp4" style="object-fit:fill" />
复制代码

增长了默认控制组件,竖屏处理,全屏播放,还有行内播放的兼容。

还有一点须要注意的是,为了用户体验更加一致,一般不建议使用 poster。直接在同位置定位一张图片和按钮,点击后直接进行视频播放。

最后还有一个没有解决的问题:

安卓端微信浏览器的视频会出现莫名的黑边,根据宽高比会出如今上下,或者左右,粗细也不一样,但始终不会消失。

查询了社区,也没有找到合理的回复解释。不知道你们有没有好的方法可以解决这个问题。




结语

文章没有过高的技术深度,基本上是以很是普通的话术来描述将一个技术点运用到项目中遇到的坑。会有许多不够严谨和成熟的地方,也欢迎你们一块儿讨论。此文也贡献给正在使用 better-scroll 的同事们,遇到相同的问题能够来借鉴讨论一下。

若有转发请告知 (固然确定是没有的了,我飘了...)

另:文章首发于 个人博客,尽管仍在建设中,但我须要您鼓励和支持,对您有帮助的话去点个赞吧。

下次见。

相关文章
相关标签/搜索