评论

Skyline|小程序页面转场动画

自定义路由:页面跳转动画随心定

开发者A:小程序跳转时,页面切换效果可以自定义实现吗?

开发者B:搞个单页自己写呢

开发者A:这个写起来代码量有点大,而且放单页里面代码太复杂了🥹

官方:来啦来啦~小程序页面转场动画可以使用小程序自定义路由来实现,赶紧往下 ⬇️ 看~


我们先来看下有无自定义路由的效果对比

  • 没有自定义路由:只能使用默认路由切换效果,从右到左推入页面
  • 使用自定义路由:支持自定义转场动画,示例下沉式路由效果

在使用默认路由的时候,只需要调用 wx.navigateTo 即可,而自定义路由,则需要先声明

1、通过 wx.router.addRouteBuilder( 命名,builder 函数 ) 来声明自定义路由

2、页面跳转新增 routeType 参数,值为上一步的命名

接着,我们来看下声明自定义路由中 builder 函数,这个函数主要是定义两个动画

  • 定义页面推入动画:结合 推入进度、推入状态 等参数,由开发者自定义计算得来
  • 定义页面被推出动画:结合 被推出进度、被推出状态 等参数,由开发者自定义计算得来

当打开一个新页面的过程中,就会触发自定义路由动画,此时有两个动画

  • 新页面:打开动画
  • 旧页面:隐藏动画

下图演示在页面 A 打开 页面 B,打开过程的动画效果:

  • 新页面:页面 B 半屏打开
  • 旧页面:页面 A 页面下沉

动画除了上述讲的动画效果之外,还有一个关键的点就是动画曲线,动画曲线可以让动画效果更加丰富。

👇 下面我们可以看出来,相同的动画效果,但是使用不同的动画曲线,展示出来的页面切换效果是很不一样的。worklet 支持了常见的动画缓动函数 Easing,开发者可以根据业务需求实现页面动画切换效果。

那么我们就来看一下页面切换动画的代码是怎么实现的~

页面的切换动画通过 worklet 函数来实现的,worklet 函数运行在 UI 线程,使得小程序可以做到 类原生动画般的体验

我们先来看下这里设计的新页面打开时的动画,在页面打开的过程中,开发者可以收到一个 primaryAnimation 的参数,这个参数表示当前页面从 0 到 1 打开的进度,此时,我们通过 handlePrimaryAnimation 来实现页面打开过程我们希望页面展示的动画效果,例如:改变页面高度、圆角、与顶部的偏移距离等。

这样,就实现页面展示的动画效果。

// 新页面:页面 B 半屏打开
const handlePrimaryAnimation = () => {
  'worklet'
  // primaryAnimation 为 builder 函数的参数,表示当前页面从 0 - 1 展示动画的进度
  // primaryAnimation 为 sharedValue 类型,当 primaryAnimation 变化时,这个函数就会被执行
  let t = primaryAnimation.value


  // 非手势触发时,可以通过动画曲线 easeInToLinear 来改变动画的进度值
  // worklet 支持了常见的动画缓动函数,开发者可以根据业务需求实现需要的页面切换动画效果
  if (!userGestureInProgress.value) {
	t = wx.worklet.Easing.bezier(0.35, 0.91, 0.33, 0.97).factory()(t)
  }
  
  const top = 0.12 // 半屏页面距离顶部的距离比例
  const selfHeight = (1 - top) * screenHeight // 半屏页面高度
  const marginTop = top * screenHeight // 半屏页面距离顶部的距离
  const translateY = selfHeight * (1 - t) // 页面动画过程中的纵向偏移值


  // 返回 AnimatedStyle,改变页面展示
  return {
    marginTop: `${marginTop}px`,
    borderRadius: '10px',
    height: `${selfHeight}px`,
    transform: `translateY(${translateY}px)`,
  }
}


同样的,页面隐藏跟页面展示是类似的,开发收到的是 secondaryAnimation 参数,表示的是页面从 1 到 0 的关闭进度,这里我们通过 handleSecondaryAnimation 来实现页面隐藏的效果。

注意:handleSecondaryAnimation 表示下一页面推入时,当前页面的隐藏动画,在下沉式动画这个案例中,handleSecondaryAnimation 是旧页面的,而 handlePrimaryAnimation 是新页面的。

// 旧页面:页面 A 页面下沉
const handleSecondaryAnimation = () => {
  'worklet'
  // secondaryAnimation 为 builder 函数的参数,表示下一个页面推入时,当前页面从 1 - 0 隐藏动画的进度
  // secondaryAnimation 为 sharedValue 类型,当 secondaryAnimation 变化时,这个函数就会被执行
  let t = secondaryAnimation.value


  // 非手势触发时,可以通过动画曲线 fastOutSlowIn 来改变动画的进度值
  if (!userGestureInProgress.value) {
	t = wx.worklet.Easing.bezier(0.4, 0.0, 0.2, 1.0).factory()(t)
  }


  const top = 0.1 // 页面距离顶部的距离比例
  const scaleRatio = 0.08 // 缩放比例
  const translateY = screenHeight * (top - 0.5 * scaleRatio) * t // 页面动画过程中的纵向偏移值
  const scale = 1 - scaleRatio * t // 缩放过程中的比例
  const radius = 12 * t // 页面圆角


  // 返回 AnimatedStyle,改变页面展示
  return {
    borderRadius: `${radius}px`,
    transform: `translateY(${translateY}px) scale(${scale})`,
  }
}


实现完动画切换的函数之后,我们需要包装到 builder 函数中并注册这个 builder 函数。

// 注册 builder 函数
wx.router.addRouteBuilder("HalfScreenDialog", HalfScreenDialogRouteBuilder)
wx.router.addRouteBuilder("ScaleTransition", ScaleTransitionRouteBuilder)


// 实现页面 B 的 builder 函数:页面打开时半屏打开
const HalfScreenDialogRouteBuilder = ({
    primaryAnimation
}) => {
    return {
        handlePrimaryAnimation // 页面 B 打开时的动画,上文中实现的 handlePrimaryAnimation 函数
    }
}


// 实现页面 A 的 builder 函数:下一个页面打开时,当前页面下沉
const ScaleTransitionRouteBuilder = ({
	primaryAnimation,
	secondaryAnimation
}) => {
    return {
		handlePrimaryAnimation, // 页面 A 打开时的动画
        handleSecondaryAnimation // 页面 B 隐藏时的动画,上文中实现的 handleSecondaryAnimation 函数
    }
}


在文章开头我们知道,声明完自定义路由之后,需要在页面跳转时指定路由类型。

到这里,通过页面跳转,返回按钮已经达到我们要的效果了。

// home.js
// 首页打开页面 A,
wx.navigateTo({
  url: 'pageA',
  routeType: 'ScaleTransition',
})


// page.js
// 页面 A 打开页面 B
wx.navigateTo({
  url: 'pageB',
  routeType: 'HalfScreenDialog',
})


我们在体验原生页面切换时,手势也是顺滑切换的重要组成部分,在上一篇 小程序手势:让半屏弹窗更顺滑 我们已经了解手势的使用,那么我们这里给页面绑定手势,支持向右、向下拖动页面返回。

我们在页面最外层嵌套一个手势组件 horizontal-drag-gesture-handler(横向滑动时触发)

当手势向右滑动时,根据触摸位置改变页面当前的状态

  • 触摸中:页面随手指拖动
  • 触摸结束:根据手势速度和位置判断关闭还是打开页面
// .wxml

  



// .js
// 根据手势状态改变页面展示状态
// this.customRouteContext 中包含当前页面定义路由 builder 时的全部变量
handleHorizontalDrag(gestureEvent) {
  "worklet";
  if (gestureEvent.state === GestureState.BEGIN) {
	// 触摸开始
        const { startUserGesture } = this.customRouteContext;
    	startUserGesture();
      } else if (gestureEvent.state === GestureState.ACTIVE) {
    	// 触摸中,实现跟随手指拖动页面效果
        const delta = gestureEvent.deltaX / windowWidth;
    	const { primaryAnimation } = this.customRouteContext;
        const newVal = primaryAnimation.value - delta;
        primaryAnimation.value = clamp(newVal, 0.0, 1.0);
      } else if (gestureEvent.state === GestureState.END) {
    	// 触摸结束
    	const { stopUserGesture, didPop } = this.customRouteContext;
    	...
    	didPop(); // 退出页面调用
    	stopUserGesture(); // 结束必须调用
      } else if (gestureEvent.state === GestureState.CANCELLED) {
        // 触摸取消
      }
    }


添加完手势之后,就可以通过手势关闭页面了~

除了案例中实现的下沉式半屏效果,自定义路由可以根据开发者需要自行定制动画。

目前,官方提供了几个常用的路由效果供大家使用,mark 这个 代码片段 即可使用。


最后一次编辑于  2023-08-03  
点赞 9
收藏
评论

7 个评论

  • 叶
    2023-07-11

    开发者工具1.06.2303220

    基础库2.30.2

    已开启skyline渲染调试

    看这个官方代码片段报错:

    Error: SystemError (webviewScriptError)

    "Failed to execute startMapper : 4 argument required"

        at Function.errorReport (WAServiceMainContext.js?t=wechat&s=1689048536596&v=2.30.2:1)


    三个示例每个都点不了

    2023-07-11
    赞同 1
    回复 1
    • 黄思程
      黄思程
      2023-08-04
      开发者工具版本比较旧,更新一下,尽量用 nightly ,其内核更新会频繁点
      2023-08-04
      回复
  • Jianbo
    Jianbo
    2023-03-02

    不错,期待正式版。

    2023-03-02
    赞同 1
    回复
  • 大中国
    大中国
    2023-08-08

    如果文章中的B页面就是首页,如何实现在首页打开一个页面的时候,实现新页面半屏展示,首页下沉呢?已经在demo代码中测试了,打开的新页面有半屏效果,但是首页没有下沉效果

    2023-08-08
    赞同
    回复 2
    • 大中国
      大中国
      2023-08-09
      官方给回复呀可以嘛
      2023-08-09
      回复
    • 大中国
      大中国
      2023-08-11回复大中国
      蹲个回复
      2023-08-11
      回复
  • 启年
    启年
    2023-03-02

    mark

    2023-03-02
    赞同
    回复
  • dreamhunter
    dreamhunter
    2023-03-02

    越来越好

    2023-03-02
    赞同
    回复
  • qiang
    qiang
    2023-03-02

    沙发

    2023-03-02
    赞同
    回复
  • 这名字不好记~寻梦港上门小程序
    这名字不好记~寻梦港上门小程序
    2023-03-02

    啥时候出正式版

    2023-03-02
    赞同
    回复 3
登录 后发表内容