Vue3 + Pinia 仿抖音,Vue 在移动端的最佳实践

Vue3 + Pinia 仿抖音,Vue 在移动端的最佳实践

zyronon/douyin: Vue3 + Pinia 仿抖音,Vue 在移动端的最佳实践 . Imitate TikTok ,Vue Best practices on Mobile (github.com)

页面跳转的动画

App.vue 里面有监听路由的路径,通过判断路由定义的顺序来判断路由是前进还是后退, 然后添加对应的动画。

1
2
3
<transition :name="transitionName">
// 这里修改动画名
</transition>

路由过渡的时候会出现白屏

这是由于每个页面都是 block 元素。使用动画过渡的时候, 上一个页面和下一个页面都是 block 元素。默认的文档流是垂直排列。 所以下一个元素会排列在上一个元素的下面。

解决办法就是将页面元素作为非 block 元素。 一般的防暑是页面根元素脱离文档流, 利用absolute 或者 fixed 等。我比较倾向于在 router-view 外面套一层 flex 元素。 这样子页面就不用每个去添加样式了。 该软件的作者也写了一篇文章来说明过渡动画应该怎么使用Vue 路由使用介绍以及添加转场动画 - 掘金 (juejin.cn)

如何判断路由是前进还是后退

该软件的作者是通过路由的定义顺序进行判断的。 这种方式有一定的局限性

⬇️作者方案

1
2
3
const toDepth = routes.findIndex((v: RouteRecordRaw) => v.path === to)
   const fromDepth = routes.findIndex((v: RouteRecordRaw) => v.path === from)
   transitionName.value = toDepth > fromDepth ? 'go' : 'back'

⬇️利用 window.history.state 控制

vue3 路由判断是前进还是后退_vue3 路由守卫判断是否回退-CSDN博客

vue-router@4、history.state和标记第一层路由的方法 - 掘金 (juejin.cn)

动画

消息页左侧的加号

用了一个自定义组件 FromBottomDialog 这个组件通过监听 modelValue 来控制显隐。这里有点没看懂。 页面都是固定的屏幕高度, 为什么还要处理 body 的 scroll。代码的注意内容是当 modelValue 为true 的时候, 手动创建一个遮罩层(添加 fade-in 动画。 0.3秒的透明度变化)。modelValue为 false 的时候, 又重新创建了一个 mask的自定义 Dom 类实例,然后隐藏这个 mask, 添加fade-out 样式类名。弹窗的动画是用了一个 transtion。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
watch(
() => props.modelValue,
(newVal: boolean) => {
   const page = document.getElementById(props.pageId)
   if (!page) return
   if (newVal) {
     pagePosition.value = _css(page, 'position')
     page.style.position = 'absolute'
     scroll.value = document.documentElement.scrollTop
     document.body.style.position = 'fixed'
     document.body.style.top = -scroll.value + 'px'

     const maskTemplate = `<div class="Mask fade-in ${props.maskMode}"></div>`
     const mask = new Dom().create(maskTemplate)
     setTimeout(() => {
       mask.on('click', (e: Event) => {
         _stopPropagation(e)
         onHide()
      })
    }, 200)
     page.appendChild(mask.els[0])
  } else {
     page.style.position = pagePosition.value || 'fixed'
     document.body.style.position = 'static'
     document.documentElement.scrollTop = scroll.value

     const mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out')
     setTimeout(() => {
       mask.remove()
    }, 250)
  }
}
)

loading 小球动画

文件是 Loading.vue

两个块元素,scale 和 transform 左右交替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

 .blue {
   background: cadetblue;
   animation: anim-blue 0.4s ease-in-out 0s infinite alternate;
}

 .red {
   background: var(--primary-btn-color);
   animation: anim-red 0.4s ease-in-out 0s infinite alternate;
}

 @keyframes anim-blue {
   from {
     transform: translate3d(0, 0, 0) scale(1);
  }
   to {
     transform: translate3d(10rem, 0, 0) scale(1.2);
  }
}
 @keyframes anim-red {
   from {
     transform: translate3d(0, 0, 0) scale(1);
  }
   to {
     transform: translate3d(-10rem, 0, 0) scale(1.2);
  }
}

我的下面下拉上滑动画

Screen-2024-05-22-231355.mp4

  • 下拉的时候背景图片会变大
    • 监听touch 事件。修改背景图片的高度。由于图片设置了cover。 高度修改的时候,就会产生图片变大的效果.松开手后, 给元素添加动画时长。然后背景图片高度复原
  • 上滑的时候顶部菜单会变黑。

Math.abs(pageMoveDistance) > 100的时候将变量改变, 修改对应的样式

评论弹窗高度变化时 video 高度变化

用了 eventbus 通讯

Screen-2024-05-22-235658.mp4

post 详情的动画

点击文章路由不跳转但是文章全屏

Screen-2024-05-23-221450.mp4

首先复制元素到’.shadow.wrap’. 这个元素作为 d。缩略图元素 opcatity 由1到0,详情元素由0到1. z-index 也变, 产生了动画效果。这里都是通过_css 操作的

其他

弹窗滑动到顶部继续滑动关闭弹窗

判断是否滑动到顶部用 wrapper.value?.scrollTop !== 0