随着小程序生态的蓬勃发展,小程序承载更多的功能,意味着更多的信息展示需求。其中卡片式设计便于对不同信息内容进行有序整理,非常适合电商交易、攻略分享等涉及多信息的场景。
当用户点击卡片时,默认的左右转场方式容易产生视觉失焦,切换感比较明显。相对而言,通过自定义路由与共享元素结合的卡片转场方式让用户的视觉关联性更强,原生效果更好。
(资料图片仅供参考)
实现上述丝滑流畅的卡片转场效果主要涉及到 3 个过程:
原卡片:照片按比例放大、文字渐隐
新页面:页面按比例放大、页面渐显
手势操作返回
为便于发现具体转场效果,上述展示为慢速展示
原卡片转场过程
首先,开发者可以通过实现原卡片的瀑布流方式,搭建页面框架。
点击查看代码
// list.wxml
然后,开发者可以使用 实现原生般丝滑的转场效果。与 的转场方式类似,开发者只需要将 grid-view 定义为卡片 card。
点击查看代码
// card.wxml
...
虽然原卡片与新页面图片元素一致,但是它们的文字内容不同。为了实现更丝滑的转场效果,原卡片在飞跃过程需要进行内容渐隐。开发者先用 this.applyAnimatedStyle 绑定 worklet 驱动动画到对应的 2 个节点,实现对应的效果:
.card__wrap 图片节点:照片按比例放大
.card_desc 文本节点:内容描述渐隐
点击查看代码
... // worklet 驱动动画:整个卡片按比例放大
// this.applyAnimatedStyle 绑定 .card_wrap 节点
this.applyAnimatedStyle(
".card_wrap‘,
() => {
"worklet‘
return {
width: this.srcWidth.value,
transform: `scale(${this.scale.value})`
}
},
// 动画执行时机配置项
{
// 是否立即执行驱动动画,这里设置 false 不立即执行
// 默认卡片 100% 宽度,不需要再改变,card 多个立即执行会执行很多次
immediate: false,
// sharedValue 更新时执行时机,sync:当前时间片,async:下一时间片
// 当前场景下需要保证共享元素与自定义路由宽度实时贴合,避免出现错位
flush: ‘sync’
}
)
// worklet 驱动动画:内容描述渐隐
// this.applyAnimatedStyle 绑定 .card_desc 节点
return {
opacity: this.opacity.value
}
同时,开发者需要通过配置项修改动画执行的时机:
immediate:设置是否立即执行驱动动画
flush:shareValue 更新时,设置 applyAnimatedStyle 的 updater 函数刷新时机
例如在当前示例中,为了保证原卡片与新页面的照片位置重叠,flush 设置 sync 在当前时间片刷新。
最后,为了避免飞跃过程造成的第一帧或者最后一帧跳变,开发者需要给共享元素绑定帧回调事件。
点击查看代码
handleFrame(data) {
"worklet’
// 飞跃过程中,改变共享元素的值来驱动动画
this.srcWidth.value = `${data.begin.width}px`
this.scale.value = data.current.width / data.begin.width
this.opacity.value = 1 - data.progress
}
新页面转场过程
在转场过程中,新页面可以通过 实现按比例放大、文字渐显的效果。
开发者只需要通过共享元素的帧回调获取 begin 、end 值,然后结合转场进度 t 计算新页面的位置、缩放比例,即可实现新页面的丝滑转场效果。
点击查看代码
// 自定义路由页面进入动画
const handlePrimaryAnimation = () => {
"worklet’
let t = primaryAnimation.value
const shareEleX = lerp(begin.left, end.left, t)
const shareEleY = lerp(begin.top, end.top, t)
const shareEleW = lerp(begin.width, end.width, t)
// 根据当前、开始、结束位置值,计算出动画过程中的值 transX = shareEleX
scale = shareEleW / end.width
transY = shareEleY - end.top * scale
return {
transform: `translate(${transX}px, ${transY}px) scale(${scale})`,
transformOrigin: "0 0‘,
opacity: PrimaryAnimation.value,
}}
手势返回操作
当用户完成内容浏览后,使用手势灵活拖拉即可返回瀑布流界面,交互体验更好。
首先,开发者需要在新页面的最外层嵌套手势组件。当手势位置变动时,新页面(通过 #fake-host 控制)的位置和大小也跟随手势灵活拖动。
点击查看代码
// detail.wxml
...
随后,开发者绑定页面驱动动画,通过 applyAnimatedStyle 给 #fake-host 绑定驱动动画。当共享变量 transX、transY 等变化时,自动改变 transform 来驱动 #fake-host 缩小。
点击查看代码
// 绑定页面驱动动画 #fake-host 为最外层节点
this.applyAnimatedStyle("#fake-host", () => {
"worklet‘
// pan 手势释放后,触发返回动画
...
// pan 手势移动阶段
const transX = this.transX.value
const transY = this.transY.value
// 根据横坐标位移比例缩放
const scale = clamp(1 - transX / screenWidth * 0.5, 0, 1)
return {
transform: `translateX(${transX}px) translateY(${transY}px) scale(${scale})`,
transformOrigin: "50% 50%‘
}
})
紧接着开发者需要绑定手势事件,根据手势拖动时拿到位置信息改变共享变量 transX、transY 的值。
点击查看代码
// 绑定手势事件
handlePanGesture(e) {
"worklet’
// 手势开始
...
// 往右滑时
if (e.state === GestureState.ACTIVE) {
const transX = e.absoluteX - this.startX.value
this.transX.value = clamp(transX, transLowerBound, transUpperBound)
this.transY.value = e.absoluteY - this.startY.value
}
// 手势结束、取消
...
}
最后,开发者需要设置透明的背景颜色,避免页面切换过程的白屏。由于 涉及 3 层页面,所以每一层的背景色均需要设置成透明,否则将无法实现透明效果。
点击查看代码
// detail.wxss 设置 page 背景色
page {
background-color: transparent; // 透明
}
// detail.json 设置 页面容器 背景色
{
“backgroundColorContent”: “#00000000” // 透明
}
// route.js 自定义路由容器背景色
const ScaleTransitionRouteBuilder = (routeContext) => {
return {
opaque: false, // 下一个页面推入时,显示前一个页面
barrierColor: “rgba(0, 0, 0, 0.3)”, // 黑色半透明
...
}
}
通过 、、、的巧妙配合,开发者即可实现完整卡片转场所需的新旧内容无缝切换效果。
持续支持开发者的渲染需求,实现在多个终端稳定、贴近原生的交互体验,赋能业务发展。欢迎各位开发者关注 了解更多动态或者访问 发现更多最佳实践。
最后,小程序渲染框架征文活动火热进行中!即日起至 6 月 1 日,开发者前往 分享标题含「小程序渲染框架」的文章内容,精选文章作者将获得微信周边礼品一套!我们一起创造更好的小程序开发生态吧!
一套礼品包括:1 个气泡狗零钱包、1 个红包背包、1 个红包吸管杯。获奖名单将会在精选文章下方评论区公布。
标签: