卡片转场,优雅流畅

微信开发者
发布于:2023-05-10
文章内容来源于公众号:微信开发者,点此可查看原文,内容版权归相关企业所有,本网站仅供交流学习。若涉及版权问题或存在侵权情况,请及时与我们联系,我们将第一时间进行删除处理。
摘要:原生般卡片转场效果,你的小程序值得拥有……
随着小程序生态的蓬勃发展,小程序承载更多的功能,意味着更多的信息展示需求。其中卡片式设计便于对不同信息内容进行有序整理,非常适合电商交易、攻略分享等涉及多信息的场景。
当用户点击卡片时,默认的左右转场方式容易产生视觉失焦,切换感比较明显。相对而言,通过 自定义路由 与 共享元素 结合的卡片转场方式让用户的视觉关联性更强,原生效果更好。
实现上述丝滑流畅的卡片转场效果主要涉及到 3 个过程:
原卡片:照片按比例放大、文字渐隐
新页面:页面按比例放大、页面渐显
手势操作返回
<scroll-view scroll-y type="custom"> <grid-view type="masonry" cross-axis-count="2"> <card wx:for="{{gridList}}" ... /> </grid-view> </scroll-view>
然后,开发者可以使用 共享元素 实现原生般丝滑的转场效果。与 打开照片 的转场方式类似,开发者只需要将 grid-view 定义为卡片 card。
<share-element key="yourkey" onFrame="handleFrame" ...> <view class="card_wrap"> <image class="card_img" fade-in src="{{item.src}}"/> <view class="card_desc">...</view> </view> </share-element>
虽然原卡片与新页面图片元素一致,但是它们的文字内容不同。为了实现更丝滑的转场效果,原卡片在飞跃过程需要进行内容渐隐。开发者先用 this.applyAnimatedStyle 绑定 worklet 驱动动画到对应的 2 个节点,实现对应的效果:
.card__wrap 图片节点:照片按比例放大
.card_desc 文本节点:内容描述渐隐
<view class="card_wrap"> <image class="card_img" /> <view class="card_desc">...</view></view>
this.applyAnimatedStyle( '.card_wrap‘, () => { 'worklet‘ return { width: this.srcWidth.value, transform: `scale(${this.scale.value})` } }, { immediate: false, flush: ‘sync’ })
return { opacity: this.opacity.value}
同时,开发者需要通过配置项修改动画执行的时机:
例如在当前示例中,为了保证原卡片与新页面的照片位置重叠,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, }}
当用户完成内容浏览后,使用手势灵活拖拉即可返回瀑布流界面,交互体验更好。
<pan-gesture-handler on-gesture-event="handlePanGesture"> <view id="fake-host"> ... </view></pan-gesture-handler>
随后,开发者绑定页面驱动动画,通过 applyAnimatedStyle 给 #fake-host 绑定驱动动画。当共享变量 transX、transY 等变化时,自动改变 transform 来驱动 #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 层页面,所以每一层的背景色均需要设置成透明,否则将无法实现透明效果。
page { background-color: transparent; }
{ “backgroundColorContent”: “#00000000” }
const ScaleTransitionRouteBuilder = (routeContext) => { return { opaque: false, barrierColor: “rgba(0, 0, 0, 0.3)”, ... }}
一套礼品包括:1 个气泡狗零钱包、1 个红包背包、1 个红包吸管杯。获奖名单将会在精选文章下方评论区公布。