个人案例
- Moe抖妹
换张壁纸,换个心情!
Moe抖妹扫码体验
- 微信小程序渲染框架Skyline初体验--比比轻壁纸实例
微信小程序的Skyline是微信小程序团队近期推出的接近原生渲染的渲染引擎,我尝试了用skyline输出了一款Uniapp+wordpress的壁纸小程序。 [图片] 今天在项目提交后我也记录下skyline开发中的各种新特性和难点。文尾附上代码。 先来看下项目上线的效果。 [视频] 可以微信扫码跳转小程序体验。 [图片] 比比轻壁纸整个项目均是skyline打造,因此需要在app.json中定义如下两个参数 "renderer": "skyline", "lazyCodeLoading": "requiredComponents", 此项目中,我也仅仅用到了skyline特性的冰山一角,因此更多的特性大家移步去官方文档中查看。 1.共享元素 最吸引我的地方的是共享元素特性,他能实现不同页面的元素无缝衔接。 官方的介绍是: 在连续的 Skyline 页跳转时,页面间 key 相同的 share-element 节点将产生飞跃特效,开发者可自定义插值方式和动画曲线。通常作用于图片,为保证动画效果,前后页面的 share-element 子节点结构应该尽量保持一致。 比比轻壁纸项目中大量使用了共享元素动画,让图片以丝滑般的交互进行呈现: [图片][图片] 实现的方法也非常简单,仅需在不同页面把图片用<share-element></share-element>包裹起来,定义相同的id即可。 默认的动画就已经很丝滑了,官方对动画交互还给出了许多自定义的属性,方便自定义更多的动画。 共享元素文档:https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/share-element.html 2.自定义路由 自定义路由真的绝了,仅仅几行css3样式属性,就能让两个页面的切换交互实现各种高难度动作。 项目中,全部的弹层交互、页面过度特效均由自定义路由轻松搞定,不同的路由交互仅需在官方提供的路由代码上修改两三行代码即可实现。 尤其是页面中有弹层内容显示或者半屏显示时,自定义路由非常实用。 [图片] 自定义路由文档:https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/custom-route.html 3.手势系统+worklet 目前项目中仅仅在图片返回时用到了手势,手势拖拽图片下滑,即可实现图片缩放回到上页 [图片] 手势系统文档:https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/gesture.html 4.吸顶布局 在小程序里,我们实现吸顶布局用到的是position:sticky。此次skyline直接给出了<sticky-header></sticky-header>实现吸顶,同时结合<sticky-section></sticky-section>能实现多重吸顶 [图片] 吸顶文档: https://developers.weixin.qq.com/miniprogram/dev/component/sticky-header.html https://developers.weixin.qq.com/miniprogram/dev/component/sticky-section.html 以上四个新特性是项目中特别值得推荐的,下面说一下开发难点 1.兼容问题 如果你是从webview项目中切换过来,你会发现页面会变的凌乱不堪,完全不堪入目。这个时候,需要一点耐心。因为新的skyline使用的类似nvue模式,即默认全部使用弹性盒模型display:flex属性,暂不支持display:block,而这个属性会导致宽度和高度变得弹性,你不定义某个子标签的宽高可能就看不到这个内容显示或者挤成一团。 经过使用后,总结了几点: a.使用display:flex最好带上flex-direction: column/rowb.形变的时候用flex-shrink: 0试试c.当横向滚动使用scroll-view并用了display:flex属性,不定义高度的话,scroll-view内容就看不到了2.常用的css属性支持度 目前正式版还未发布,常用的伪类、var等还未支持,需要这些特性的需要再等等。 导致的问题包括图标字体的使用等,可以看看官方给出的解决方案或者使用svg图标 特性兼容进度:https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/status.html 3.第三方组件问题 如果你的项目之前用到了第三方组件,可能在skyline模式下大量兼容报错,目前考虑换微信小程序官方的组件或者自己写自定义组件 4.项目发布后还是webview框架? 项目提交发布后还需要做全量skyline测试 https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/migration/#%E9%85%8D%E7%BD%AE-We-%E5%88%86%E6%9E%90-AB-%E5%AE%9E%E9%AA%8C 附上项目的小程序demo代码(非uniapp源码)仅供大家学习 https://github.com/shiheme/smartgallery
2023-04-27 - 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 - Skyline|在小程序实现原生相册的效果
相册在日常生活中经常使用到,如手机自带相册、朋友圈、商品展示图、评论贴图等等,都经常用到相册的能力。 👇下面演示 iOS 原生相册、朋友圈等相册使用效果,我们可以看到图片切换非常顺滑,视觉焦点不变。 [图片] 😭 但是在小程序中,页面切换会有明显的切换感。用户焦点会丢失,缺少视觉关联性。 [图片] 共享元素🔥 为了丰富用户交互效果、提升用户体验、增强视觉关联性,小程序支持了页面间的共享元素 下图展示有无共享元素的页面切换效果,可以看出使用共享元素之后,转场动画更灵活 [图片] 共享元素 经常作用在图片上,例如上面示例中的相册效果,是那么共享元素动画要怎么实现呢? 在页面跳转时,两个页面 key 相同的 share-element 组件则会产生飞跃的过渡效果 [图片] 在上一篇文章中,我们学习了 页面转场动画,共享元素动画跟页面转场动画是类似的,同样是在页面切换间的动画。 动画进度、时间 与 路由进度、时间保持一致(非自定义路由也支持共享元素动画) 在共享元素飞跃的过程中,前后页面图片的裁剪方式(mode) 可能不一致 这种情况下容易导致图片突然跳变,所以我们需要在飞跃的过程中改变图片的大小来保证平滑飞跃 [图片] 在共享元素动画进行的过程中,share-element 可以收到 onFrame 表示动画帧回调 我们可以在帧回调中处理内部元素的显示 例如:我们这里通过在帧回调中改变图片宽高来达到平滑飞跃的效果 // .wxml // .js // 初始化 attached() { this.aspectRatio = shared(0) this.curRect = shared(undefined) // 绑定 worklet 动画 this.applyAnimatedStyle('.img', () => { 'worklet' const curRect = this.curRect.value return { left: `${curRect.left}px`, top: `${curRect.top}px`, width: `${curRect.width}px`, height: `${curRect.height}px` } }) }, // 获取图片初始宽高比 onImageLoad(e) { const { width, height } = e.detail this.aspectRatio.value = width / height }, // 动画帧回调,调整图片大小 onFrame(data) { 'worklet' // 当前帧容器的宽高、进度等信息 const { begin, end, progress, direction } = data ... // 根据图片初始宽高比、共享元素容器、动画进度等计算出变化过程中的值 this.curRect.value = { left = lerp(begin.left, end.left, t), top = lerp(begin.top, end.top, t), width = lerp(begin.width, end.width, t), height = lerp(begin.height, end.height, t), } } 更多共享元素动画原理请查看 官方文档 手势搭配打开图片之后,我们经常需要用到手势来操作图片,如缩放、移动、双击等等 [图片] 我们上次学过的 手势系统 又派上用场啦 通过监听手势事件配合 worklet 函数即可在小程序实现图片预览效果 👇 下面演示缩放手势的处理,除了缩放之外,相册在手势处理上还有很多复杂的逻辑,包括惯性、边界逻辑判断等 点击查看更多相册相关的手势操作 // .wxml // 绑定缩放手势 let sharedValues = this.sharedValues ?? [] // .js // 绑定缩放 this.applyAnimatedStyle('#image', () => { 'worklet' // worklet 函数,sharedValues 变化时,函数会立即执行 return { transform: `scale(${sharedValues[SCALE].value})` } }) // 监听缩放 onScale(evt) { 'worklet' // 连续的手势状态 && 双指放缩 if (evt.state === GestureState.ACTIVE && evt.pointerCount === 2) { // 计算出当前真正的缩放值 sharedValues[SCALE].value = evt.scale / sharedValues[TEMP_LAST_SCALE].value sharedValues[TEMP_LAST_SCALE].value = evt.scale } } 最后,我们来看下小程序实现出来的相册跟原生相册的使用对比,在小程序也可以顺滑的实现类原生的效果啦~ [图片] 目前,同程旅行 已经上线了共享元素结合手势的相册效果,mark这个 相册源码 直接接入到你的小程序吧~ [视频]
2023-08-03