【山竹记账前端-react】5.一个小时完成四个页面


大纲链接 §

[toc]


1. 第一个页面的HTML完成 ⇧

修改路由相关,保证跳转符合预期 ⇧

目前根路由不跳转,直接空白,修改路由表 @/route/routes.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { RedirectToWelcome1 } from '@/components/RedirectToWelcome1'
import { welcomeRouter } from '@/route/welcomeRouter'
import { redirect } from 'react-router-dom'

export const routes = [
  {
    path: '/',
    errorElement: (<RedirectToWelcome1 />),
    loader: () => redirect('/welcome/1'),
  },
  welcomeRouter,
]

处理路由类型 @/route/router.tsx

1
2
3
4
5
6
import { routes } from '@/route/routes'
import { createBrowserRouter } from 'react-router-dom'

type AppRouterType = ReturnType<typeof createBrowserRouter>

export const router: AppRouterType = createBrowserRouter(routes)

修改欢迎页路由 @/route/welcomeRouter.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { RedirectToWelcome1 } from '@/components/RedirectToWelcome1'
import { WelcomeMainLayout } from '@/layouts/WelcomeMainLayout'
import { Welcome1 } from '@/pages/Welcome1'
import { Welcome2 } from '@/pages/Welcome2'
import { Welcome3 } from '@/pages/Welcome3'
import { Welcome4 } from '@/pages/Welcome4'

export const welcomeRouter = {
  path: '/welcome/',
  element: <WelcomeMainLayout />,
  errorElement: (<RedirectToWelcome1 />),
  children: [
    { path: '1', element: (<Welcome1 />) },
    { path: '2', element: (<Welcome2 />) },
    { path: '3', element: (<Welcome3 />) },
    { path: '4', element: (<Welcome4 />) },
  ],
}

调整结构,引入图标资源 ⇧

  • 原先四个欢迎页面是整体路由切换的,现在改为头部页脚不同,中间卡片部分滑动

修改 @/layouts/WelcomeMainLayout.tsx 结构

 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
import { animated, useTransition } from '@react-spring/web'
import { useRef } from 'react'
import { NavLink, useLocation, useOutlet } from 'react-router-dom'
import logo from '@/assets/icons/mangosteen.svg'

export const WelcomeMainLayout: React.FC = () => {
  // ...
  return (
    <div>
      <header>
        <img alt="logo" src={logo} />
        <h1>山竹记账</h1>
      </header>

      <main>
        {transitions((style, pathname) => (
          <animated.div key={pathname} style={style}>
            {currentOutlet}
          </animated.div>
        ))}
      </main>

      <footer>
        <NavLink to="/welcome/1">跳过</NavLink>
      </footer>
    </div>
  )
}

第一个页面卡片结构 @/pages/Welcome1.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { NavLink } from 'react-router-dom'
import pig from '@/assets/icons/pig.svg'

export const Welcome1: React.FC = () => {
  return (
    <div className="wrapper">
      <img alt="pig" src={pig} />
      <article>
        <h2 className="slogan">
          <p>会挣钱</p>
          <p>还要会省钱</p>
        </h2>
      </article>
      <section>
        <NavLink to="/welcome/2">下一页</NavLink>
      </section>
    </div>
  )
}

2. 第一个页面的CSS完成 ⇧

还是在tsx结构中写 @/layouts/WelcomeMainLayout.tsx

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { animated, useTransition } from '@react-spring/web'
import { useRef } from 'react'
import { NavLink, useLocation, useOutlet } from 'react-router-dom'
import logo from '@/assets/icons/mangosteen.svg'
import { cn } from 'cn'

export const WelcomeMainLayout: React.FC = () => {
  // ...
  return (
    <div className={cn(
      'relative w-screen h-screen overflow-hidden',
      'flex flex-col justify-center items-stretch',
      'bg-[#5f34bf]',
    )}>
      <header
        className="mt-[4em]"
        shrink-0
        text-center
        flex
        flex-col
        items-center>
        <img alt="logo" src={logo} w-64px h-64px />
        <h1 className="text-[#d4d4ee] text-[2em]">山竹记账</h1>
      </header>

      <main
        className={cn(
          'flex grow-1 shrink-1',
          'bg-white relative m-[16px] rounded-[8px]',
          'mb-64px',
        )}>
        {transitions((style, pathname) => (
          <animated.div
            key={pathname}
            style={style}
            className="flex">
            {currentOutlet}
          </animated.div>
        ))}
      </main>

      <section
        className={cn(
          'absolute fixed right-[0.5em] top-[0.25em]',
          'text-[#d4d4ee] text-[1.5em]',
        )}>
        <NavLink to="/welcome/1">跳过</NavLink>
      </section>
    </div>
  )
}

@/pages/Welcome1.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { NavLink } from 'react-router-dom'
import pig from '@/assets/icons/pig.svg'

export const Welcome1: React.FC = () => {
  return (
    <div className="grow-1 flex flex-col items-center justify-around">
      <img alt="pig" src={pig} className="mt-[25%]" />
      <article>
        <h2 className="text-center flex flex-col items-center text-[2em]">
          <p>会挣钱</p>
          <p>还要会省钱</p>
        </h2>
      </article>
      <section className="text-[var(--primary-color)] mb-[84px] text-[2em] font-bold">
        <NavLink to="/welcome/2">下一页</NavLink>
      </section>
    </div>
  )
}

3. 使用手机调试页面 ⇧

真机调试页面

  • 搜索 google chrome android remote debug

  • 手机端访问项目网址

    • PC 启动项目时命令添加 pnpm dev --host
    • PC 运行命令查看 ip:ipconfig,找到和手机同一个无线网下的ip:xx.xxx.xxx.xx
    • 使用 http://xx.xxx.xxx.xx/... 替换 http://localhost/... 访问项目页面
    • PC 上打开防火墙的ip白名单,保证ip下的端口可以访问PC
    • 搜索 windows 端口加入白名单
    • win11 高级安全 Windows Defender 防火墙
    • 入站规则 -> 新建规则
    • 选择:端口
    • 填入:特定端口 5173,和项目地址中端口一致即可
    • 允许连接
    • 全选规则:域、专用、共用
    • 名称任意,完成
  • 使用数据线连接 PC 和 手机

  • 手机端打开开发者选项

    • 启用开发者选项中 USB 调试
    • 此时会弹出确认框:允许USB调试吗?
    • 可选:一律允许使用这台计算机进行调试
    • 确定
  • PC端打开 chrome,地址栏输入 chrome://inspect/#devices

    • 找到 Devices 中你的设备,点击 inspect,稍等弹出开发者工具页面即可
    • 确保页面正在前台运行,而不是切换到后台休眠
  • PC端打开 edge,地址栏输入 edge://inspect/#devices

    • 后续类似 chrome
  • 可以查看浏览器控制台,进行调试

下载使用 Minimal ADB and Fastboot

  • 手机端关闭 USB 调试
  • 断开PC和手机的连接线
  • 删除 ~/.android/ 中全部文件
  • 打开 CMD 终端
  • 运行文件夹中 cmd-here.exe
  • 输入运行命令 adb devices
  • 再次连接手机与PC
  • 手机端打开 USB 调试

真机调试页面


4. 四个页面完成 ⇧

参考第一个页面完成后续三个 @/pages/Welcome2.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { NavLink } from 'react-router-dom'
import clock from '@/assets/icons/clock.svg'

export const Welcome2: React.FC = () => {
  return (
    <div className="grow-1 flex flex-col items-center justify-around">
      <img alt="pig" src={clock} className="mt-[25%]" />
      <article>
        <h2 className="text-center flex flex-col items-center text-[2em]">
          <p>每日提醒</p>
          <p>不会遗漏每一笔账单</p>
        </h2>
      </article>
      <section className="text-[var(--primary-color)] mb-[84px] text-[2em] font-bold">
        <NavLink to="/welcome/3">下一页</NavLink>
      </section>
    </div>
  )
}

调整布局组件样式 @/layouts/WelcomeMainLayout.tsx

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { animated, useTransition } from '@react-spring/web'
import { useRef } from 'react'
import { NavLink, useLocation, useOutlet } from 'react-router-dom'
import logo from '@/assets/icons/mangosteen.svg'
import { cn } from 'cn'

export const WelcomeMainLayout: React.FC = () => {
  // ...

  return (
    <div className={cn(
      'relative w-screen h-screen overflow-hidden',
      'flex flex-col justify-center items-stretch',
      'bg-[#5f34bf]',
    )}>
      <header
        className="mt-[4em]"
        shrink-0
        text-center
        flex
        flex-col
        items-center>
        <img alt="logo" src={logo} w-64px h-64px />
        <h1 className="text-[#d4d4ee] text-[2em]">山竹记账</h1>
      </header>

      <main
        className={cn(
          'relative flex grow-1 shrink-1',
          'm-[16px] mb-64px',
        )}>
        {transitions((style, pathname) => (
          <animated.div
            key={pathname}
            style={{ ...style, ...extraStyle }}
            w="100%"
            h="100%"
            className={cn(
              'bg-white rounded-8px p16px',
              'flex items-center justify-center grow-1 shrink-1',
            )}>
            {window.console.log('pathname', pathname)}
            {mapRef.current[pathname]}
          </animated.div>
        ))}
      </main>

      <section
        className={cn(
          'absolute fixed right-[0.5em] top-[0.25em]',
          'text-[#d4d4ee] text-[1.5em]',
        )}>
        <NavLink to="/welcome/1">跳过</NavLink>
      </section>
    </div>
  )
}

5. 优化动画的两个细节 ⇧

UnoCSS 样式调试

  • 添加 b-1 b-solid b-red 标明盒子的边界

动画过渡问题 ⇧

动画过程中会animated.div出现两个元素,后面的元素默认会被挤下去,添加绝对定位

  • 弹性元素 main 由于添加了 grow-1,即使内部元素绝对定位了也会自动撑开
  • 给 main 添加相对定位
  • 定义钩子 const [extraStyle, setExtraStyle] = useState({ position: 'relative',})
    • 将 extraStyle 合并到 animated.div 元素的行内样式中
    • 初始默认时相对定位,动画开始时设置成绝对定位,动画结束恢复相对定位
  • 使用transitions动画配置中的生命周期函数 onStart、onRest
  • 微调动画元素周边的空白填充
    • 将 animated.div 确保宽度占满,动画元素连贯,去除 main 的内边距
    • 将白色背景放到里层 div 元素,并添加内边距
    • 调整 display 弹性布局样式,子元素撑开 grow-1

@/layouts/WelcomeMainLayout.tsx

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import { animated, useTransition } from '@react-spring/web'
import type { ReactNode } from 'react'
import { useRef, useState } from 'react'
import { NavLink, useLocation, useOutlet } from 'react-router-dom'
import logo from '@/assets/icons/mangosteen.svg'
import { cn } from 'cn'

export const WelcomeMainLayout: React.FC = () => {
  const mapRef = useRef<Record<string, ReactNode>>({})
  const { pathname } = useLocation()
  // 获取当前的 outlet
  const currentOutlet = useOutlet() as React.ReactElement
  mapRef.current[pathname] = currentOutlet

  const [extraStyle, setExtraStyle] = useState<Record<string, string | number>>({ position: 'relative' })
  // 3. 根据方向动态设置动画的初始位置和退出位置
  const transitions = useTransition(pathname, {
    onStart: () => { setExtraStyle({ position: 'absolute' }) },
    onRest: () => { setExtraStyle({ position: 'relative' }) },
    from: { opacity: 0, transform: 'translate3D(100%, 0, 0)' },
    enter: { opacity: 1, transform: 'translate3D(0%, 0, 0)' },
    leave: { opacity: 0, transform: 'translate3D(-100%, 0, 0)' },
    // 物理参数微调:让过渡更轻快
    config: { tension: 280, friction: 30, duration: 300 },
  })

  return (
    <div className={cn(
      'overflow-clip',
      'bg-[#5f34bf]',
      'h-screen pb-16px',
      'flex flex-col items-stretch',
    )}>

      <header shrink-0 text-center pt-4em>
        <img alt="logo" src={logo} w-64px h-69px inline />
        <h1 text="#d4d4ee" text-2em>山竹记账</h1>
      </header>

      <main
        className={cn(
          'grow-1 shrink-1 mb-2em relative overflow-clip',
        )}>
        {transitions((style, pathname) => (
          <animated.div
            key={pathname}
            style={{ ...style, ...extraStyle }}
            className={cn(
              'flex h-100% w-100% p-16px',
            )}>
            <div
              className={cn(
                'grow-1 flex justify-center items-center',
                'bg-white rounded-8px',
              )}>
              {mapRef.current[pathname]}
            </div>
          </animated.div>
        ))}
      </main>

      <section
        className={cn(
          'fixed right-[0.5em] top-[0.25em]',
          'text-[#d4d4ee] text-[1.5em]',
        )}>
        <NavLink to="/welcome/1">跳过</NavLink>
      </section>
    </div>
  )
}
  • 可复现步骤
    • 先调成慢 4G 网络
    • 删掉浏览器 element 面板中的相关节点
    • 刷新页面

防止动画元素抖动 ⇧

  • 给内部所有 img 设置宽高
    • 当初始加载图片时,由于没有设置宽高,所以会抖动
  • 使用边距来辅助定位时,动画过程中,也会发生抖动
    • 使用 transform: translate() 来调整元素定位,代替 margin 或 padding

@/pages/Welcome1.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { NavLink } from 'react-router-dom'
import pig from '@/assets/icons/pig.svg'

export const Welcome1: React.FC = () => {
  return (
    <div className="h-[100%] overflow-clip flex flex-col justify-around items-center">
      <img alt="pig" src={pig} w-128px h-130px className="translate-y-[50%]" />
      <article>
        <h2 className="text-center flex flex-col items-center text-[2em]">
          <p>会挣钱</p>
          <p>还要会省钱</p>
        </h2>
      </article>
      <section className="text-[var(--primary-color)] text-[2em] font-bold translate-y-[-100%]">
        <NavLink to="/welcome/2">下一页</NavLink>
      </section>
    </div>
  )
}

·未完待续·

参考文章

相关文章

  • 无

  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名