• 记录--用 Vue 实现原神官网的角色切换效果


    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

    前言

    为了更好的了解原神角色,我模仿官网做了一个角色切换效果,在做的过程当中也总结了一些技术点。

    为了让大家更好的体验,我兼容了 PC 端和移动端,建议在 PC 端查看效果更佳。接下来就为大家简单的分享一下!

    话不多说,原神启动!!

    效果

    先看一下官网的效果: ys.mihoyo.com/main/charac…

    我写的真实效果:chenyajun.fun/#/ysRoleSwi…

    技术点

    一、底部角色横向滚动效果

    1、原理

    外层容器:就是底部角色可视窗口。

    内层容器: 用来存放角色头像,点击左右键或者角色需要滚动的容器。

    滚动元素: 就是我们看到的一个个角色头像。

    首先我们使用外层容器来包裹所有的角色信息,同时使外层容器overflow:hidden。

    其次我们通过点击左右按键或者任意角色来确定当前所处的 index。

    最后通过固定角色宽度 ✖ index 来控制内层容器的left值,从而移动内层容器位置,最终显示当前激活的角色头像。(注:移动端实现效果不同,下面单独讲解) 

    2、要求

    要求1:如果处于最左(右)侧的位置,点击左右键或者前三个角色头像不进行位移移动,只显示角色切换激活状态

    要求2:处于最左侧并且点击左键需要角色容器第一个为倒数第六个,也就是最后一栏。相反,处于最右侧并且点击右键需要移动到第一个,也就是第一栏。

    要求3:点击中间部分永远保持在角色容器的第三个激活状态。

    3、代码实现

    大家可以根据HTML结构去理解代码

    1. <div class="role-outer" :style="{ width: isPC ? '830px' : '320px' }">
    2. <template>
    3. <div class="left-arrow" :style="{ backgroundImage: `url(${leftSwitchArrow})` }" @click="lastPage"></div>
    4. <div class="right-arrow" :style="{ backgroundImage: `url(${rightSwitchArrow})` }" @click="nextPage"></div>
    5. </template>
    6. <div class="role-contener">
    7. //内部容器
    8. <div
    9. class="role-inner"
    10. :class="{ activeTranstion: isCloseTranstion }"
    11. :style="{ left: isPC ? moveDistancePC : moveDistanceMobile }"
    12. ref="element"
    13. >
    14. //角色
    15. <template>
    16. <div
    17. v-for="(item, index) in yuRoleMes"
    18. class="role-item-pc"
    19. :style="{
    20. backgroundPosition: $index === index ? '0 -132px' : '',
    21. backgroundImage: `url(${bottomRoleBac})`,
    22. }"
    23. :key="index"
    24. @click="handleRoleSwitchPC(index)"
    25. >
    26. <img class="item-avater" :src="item.roleAvatar" />
    27. <p class="item-name" :style="{ color: $index === index ? '#000' : '#fff' }">{{ item.roleName }}</p>
    28. </div>
    29. </template>
    30. </div>
    31. </div>
    32. </div>
    33. </div>

    可以看到我们通过 moveDistancePC 变量来控制内层容器的移动位移,接下来主要分析一下怎么控制 moveDistancePC !

    1.点击操作

    通过点击上一页、下一页以及角色信息来改变索引 $index。

    1. // 上一页
    2. function lastPage() {
    3. if ($index.value === 0) {
    4. $index.value = yuRoleMes.value.length - 1
    5. return
    6. }
    7. $index.value--
    8. }
    9. // 下一页
    10. function nextPage() {
    11. // 到最后一页
    12. if ($index.value === yuRoleMes.value.length - 1) {
    13. $index.value = 0
    14. return
    15. }
    16. $index.value++
    17. }
    18. // 点击角色操作
    19. function handleRoleSwitchPC(index) {
    20. isCloseTranstion.value = false
    21. $index.value = index
    22. }

    当处于第一个并且点击左键时,直接将索引赋值为最后一个角色。

    1. if ($index.value === 0) {
    2. $index.value = yuRoleMes.value.length - 1
    3. return
    4. }

    当处于最后一个并且点击右键时,直接将索引赋值为0到第一个。

    1. // 到最后一页
    2. if ($index.value === yuRoleMes.value.length - 1) {
    3. $index.value = 0
    4. return
    5. }

    2.索引控制移动位移

    通过索引*角色宽度来进行位置的移动,不过需要进行界限判断,144为宽度(110px)+margin-right(34px)。

    1. // 每次点击移动距离
    2. const moveDistance = computed(() => {
    3. const firstThree = $index.value < 3
    4. if (firstThree) {
    5. return 0
    6. }
    7. const lastThree = $index.value > yuRoleMes.value.length - 4
    8. if (!lastThree) {
    9. return ($index.value - 2) * -144
    10. }
    11. return (yuRoleMes.value.length - 6) * -144
    12. })
    13. //对容器进行位移赋值
    14. const moveDistancePC = computed(() => {
    15. return moveDistance.value + 'px'
    16. })
    情况一:

    如果$index处于前三个,那么位置不变永远为0。

    1. const firstThree = $index.value < 3
    2. if (firstThree) {
    3. return 0
    4. }
    情况二:

    如果$index处于中间部分,永远保持为当前的第三个激活即可。

    ($index.value - 2) 意思为如果直接取 $index 那么角色位置会在第一个,保持在第三个就往后面移动两位,故减去两个即可。

    1. const lastThree = $index.value > yuRoleMes.value.length - 4
    2. if (!lastThree) {
    3. return ($index.value - 2) * -145
    4. }
    情况三: 

    如果$index处于后三个,那么位置不变,永远处于倒数第六个。

     return (yuRoleMes.value.length - 6) * -145
    

    二、背景图片放大缩小切换效果

    我们可以看到每个场景下会有两张背景图片在不断的切换,第一张结束之后第二张出现,两张图片循环播放,若隐若现。

    代码实现

    这一部分是直接模仿的官方的样式,直接说一下实现原理!

    HTML

    1. <div class="background-wrapper">
    2. <div
    3. class="role-background role-bg1"
    4. :style="{
    5. backgroundImage: outerTwoBackground[0],
    6. }"
    7. >div>
    8. <div
    9. class="role-background role-bg2"
    10. :style="{
    11. backgroundImage: outerTwoBackground[1],
    12. }"
    13. >div>
    14. div>

    需要使第一张背景永远处于存在且向外扩大的状态,通过关键帧动画控制第二张的显示隐藏,从而就达到了两张照片的交替显示隐藏,是不是很巧妙?

    相关css代码:

    1. // 第一张永远存在进行扩张
    2. .role-bg1 {
    3. animation: breath 80s infinite linear;
    4. opacity: 1;
    5. }
    6. // 第二张也在扩张,只不过每次从显示到隐藏需要15
    7. .role-bg2 {
    8. animation: bg-change 15s infinite linear, breath 80s infinite linear;
    9. opacity: 0;
    10. }
    11. // 用于第二张的显示隐藏
    12. @keyframes bg-change {
    13. 48% {
    14. opacity: 0;
    15. }
    16. 50% {
    17. opacity: 1;
    18. }
    19. 98% {
    20. opacity: 1;
    21. }
    22. 100% {
    23. opacity: 0;
    24. }
    25. }
    26. // 用于向外扩张效果
    27. @keyframes breath {
    28. 0% {
    29. transform: scale(1);
    30. }
    31. 50% {
    32. transform: scale(1.2);
    33. }
    34. 100% {
    35. transform: scale(1);
    36. }
    37. }

    三、背景角色切换效果

     

    到我们在切换角色的时候,第一个角色会移出,同时下一张照片会移入,并且伴有动画,这个效果也是css实现的。

    1、原理

    我们将所有的图片起初都定位到外面,默认显示第一张,而当我们切换照片时,我们设置需要显示的照片的 right 值,同时设置透明度,在这个过程中增加过渡动画即可。 ​

     2、实现代码

    HTML结构

    1. <!-- 角色背景 -->
    2. <div class="role-wrapper">
    3. <div class="role-box">
    4. <img
    5. v-for="(item, index) in yuRoleMes"
    6. class="role-picture"
    7. :class="[index === $index ? show-background-pc' : 'hide-background']"
    8. :key="index"
    9. :src="item.roleBackground"
    10. />
    11. </div>
    12. </div>

    我设置了两个类show-background-pc,hide-background前者用来显示切换的照片,index === $index时显示点击的照片,否则调整right值直接隐藏,两个都加上过渡动画,这样显示隐藏都会有效果了。

    1. .show-background-pc {
    2. right: 0;
    3. opacity: 1;
    4. transition: all 0.3s;
    5. }
    6. .show-background-mobile {
    7. right: 445px;
    8. opacity: 1;
    9. transition: all 0.3s;
    10. }

    移动端

     1、底部角色滑动效果

    2、点击和触摸移动事件冲突

    我在做的过程中发现点击事件和触摸事件发生了冲突,就是我既要滑动这个容器又要进行点击。

    我采取的办法是结束位置减去起始位置是否为0,如果为0则视为点击事件!

    1. moveDistanceX.value = endX.value - startX.value
    2. // 如果是点击滑动
    3. if (moveDistanceX.value === 0) {
    4. isClickMobile(e)
    5. return
    6. }

    如果为true则处理点击事件,否则处理移动事件即可,如果是点击事件的话,逻辑和 PC 相似,这里不再重复讲解,主要说一下滑动移动。

    3、触摸移动角色容器

    我们在触摸滑动容器时,容器需要跟着一起滑动

    这里有一些变量,大家可能需要看一下

    1. const isCloseTranstion = ref(false) //控制是否显示动画效果
    2. const startX = ref(0) //记录开始位置
    3. const endX = ref(0) //记录结束位置
    4. const moveDistanceX = ref(0) //按下抬起滑动距离
    5. const recordLastMove = ref(0) //记录上次滑动的距离,用于下次累加
    6. const moveDistanceM = ref(0) //最终移动的距离

    1.开始触摸

    1. // 触摸开始
    2. function handleTouchStart(e) {
    3. isCloseTranstion.value = true // 开始移动 关闭动画
    4. startX.value = e.touches[0].pageX || e.changedTouches[0].pageX
    5. }

    2.触摸移动

    触摸移动的话,下面的容器需要实时跟着一起移动,也就是我们移动的位移需要实时的赋值上去,并且要累加上上次的位置,比如第一次移动到10,下次移动就是从10的基础上进行移动。

    1. //最终移动的距离赋值为容器left值即可
    2. const moveDistanceMobile = computed(() => {
    3. return moveDistanceM.value + 'px'
    4. })
    5. // 触摸移动
    6. function handleTouchMove(e) {
    7. e.preventDefault()
    8. isCloseTranstion.value = true // 开始移动 关闭动画
    9. moveDistanceX.value = (e.changedTouches[0].pageX || e.touches[0].pageX) - startX.value // 计算移动距离
    10. moveDistanceM.value = recordLastMove.value + moveDistanceX.value
    11. }

    3.触摸抬起

    容器的最终位置这里就需要分情况进行处理了,满足相应的条件处理相应的逻辑即可。

    情况一:

    如果只有一栏照片或者直接往右边移动,那就只进行实时同步移动,只不过移动结束还恢复到原来位置即可。

    1. // 如果照片小于五张不进行位移移动
    2. if (yuRoleMes.value.length < 5) {
    3. moveDistanceM.value = 0
    4. recordLastMove.value = 0
    5. return
    6. }
    情况二:

    已经滑到最后一栏继续左滑,这时已经超出了最大界限,将最终位置定格在最大界限的位置

    1. // 最后一栏的位置
    2. const rightMaxValue = (yuRoleMes.value.length - 5) * -64
    3. //在最后一栏继续往左边滑动
    4. if (moveDistanceM.value < rightMaxValue) {
    5. moveDistanceM.value = rightMaxValue
    6. recordLastMove.value = rightMaxValue
    7. return
    8. }
    情况三:

    除了以上情况,也就是在中间正常滑动,我们进行赋值即可,使用 Math.round 来保证我们移动的都是每个角色的整数倍即可。

    1. // 其他情况
    2. moveDistanceM.value = recordLastMove.value + Math.round(moveDistanceX.value / 64) * 64
    3. recordLastMove.value = Math.round(moveDistanceM.value / 64) * 64

    总结

    声明:以上所有信息材料均来之于原神官网ys.mihoyo.com/main/charac…

    上面我将功能进行了分开描述,PC 端和移动端最终效果不一样,所以代码实现也不一样,但其实实现逻辑相似,只不过需要对不同的情况进行处理。

    另外就是每个情况需要考虑细致,我在写的过程中,总会有落下的问题,也就是测试用例不太全面,如果大家有发现不对的地方,可以在下方进行留言,我看到会进行及时更改的。

    写作不易,你的赞就是我最大的动力,觉得写的不错的,可以给点个赞呢~

    源码:

    全部源码已经同步上传到了 GitHub

    觉得写的不错了,可以给作者点一下star⭐

    GitHub:chenyajun-create/ysRoleSwitch (github.com)

    本文转载于:

    https://juejin.cn/post/7277802797474021410

    如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

     

  • 相关阅读:
    响应式UI组件集DevExtreme v22.1.5,9月全新发布!
    ADBMS1818驱动程序解析
    9 JDBC
    数字展厅是什么,数字展厅有哪些优势?
    (※)力扣刷题-栈和队列-用队列实现栈
    20 【rem适配布局】
    关于利用webase-front节点控制台一键导出的java项目解析
    “五度情报站”微信小程序上线,让情报信息唾手可得!
    ffmpeg命令行处理视频,学习记录
    星卫士&陈卫俊表示要用心打造做真正好用、良心的健康手表
  • 原文地址:https://blog.csdn.net/qq_40716795/article/details/132984842