美文网首页
Vue实现前端动画抓抓乐抽奖

Vue实现前端动画抓抓乐抽奖

作者: 王果果 | 来源:发表于2022-01-14 23:30 被阅读0次

CSS3 动画卡顿性能优化
  • 明确以下几个概念:单线程,主线程和合成线程,浏览器是单线程运行的(注意,是执行,并不是说浏览器只有1个线程,而是运行时,runing),但实际上浏览器的2个重要的执行线程,这 2 个线程协同工作来渲染一个网页:主线程和合成线程。
  • 主线程负责:运行 JavaScript;计算 HTML 元素的 CSS 样式;页面的布局;将元素绘制到一个或多个位图中;将这些位图交给合成线程。
    合成线程负责:通过 GPU 将位图绘制到屏幕上;通知主线程更新页面中可见或即将变成可见的部分的位图;计算出页面中哪部分是可见的;计算出当你在滚动页面时哪部分是即将变成可见的;当你滚动页面时将相应位置的元素移动到可视区域。
那么为什么会造成动画卡顿呢?

原因就是主线程和合成线程的调度不合理。

  • 在使用height,width,margin,padding作为transition的值时,会造成浏览器主线程的工作量较重,例如从margin-left:-20px渲染到margin-left:0,主线程需要计算样式margin-left:-19px,margin-left:-18px,一直到margin-left:0,而且每一次主线程计算样式后,合成进程都需要绘制到GPU然后再渲染到屏幕上,前后总共进行20次主线程渲染,20次合成线程渲染,20+20次,总计40次计算。
  • 主线程的渲染流程,可以参考浏览器渲染网页的流程:
    使用 HTML 创建文档对象模型(DOM)
    使用 CSS 创建 CSS 对象模型(CSSOM)
    基于 DOM 和 CSSOM 执行脚本(Scripts)
    合并 DOM 和 CSSOM 形成渲染树(Render Tree)
    使用渲染树布局(Layout)所有元素
    渲染(Paint)所有元素
    也就是说,主线程每次都需要执行Scripts,Render Tree ,Layout和Paint这四个阶段的计算。
  • 而如果使用transform的话,例如tranform:translate(-20px,0)到transform:translate(0,0),主线程只需要进行一次tranform:translate(-20px,0)到transform:translate(0,0),然后合成线程去一次将-20px转换到0px,这样的话,总计1+20计算。
  1. 假设我们要一个元素的 height 从 100 px 变成 200 px,就像这样:
div {
  height: 100px;
  transition: height 1s linear;
}
 
div:hover {
  height: 200px;
}

可能会比较耗时,容易出现卡顿现象

  1. 而使用 transform:scale 实现,减少主线程的计算次数,提高动画性能
div {
  transform: scale(0.5);
  transition: transform 1s linear;
}
 
div:hover {
  transform: scale(1.0);
}

其次还可以通过开启硬件加速的方式优化动画,开启3d加速
webkit-transform: translate3d(0,0,0);
-moz-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
-o-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);

总结解决 CSS3 动画卡顿方案
  1. 尽量使用 transform 当成动画熟悉,避免使用 height,width,margin,padding 等;
  2. 要求较高时,可以开启浏览器开启 GPU 硬件加速
    源链接:https://www.jb51.net/article/147736.htm

抓抓乐抽奖代码实现(移动端)

  1. 前端动画展示的一种抽奖形式
  2. 有上下两排奖品左右移动
  3. 点击抽奖按钮上方夹子下落,奖品停止移动,随机抓取移动的奖品上升
  4. 夹子到达初始位置,奖品返回初始位置并继续移动
  • template
<template>
    <div class="shopping-grab" :style="{'background-image': `url(${styleConfig.grab_bg})`}" >
        <!-- 动画部分 -->
        <div class="grab-box">
            <!-- 钩子部分 -->
            <div class="hook-box">
                <!-- 绳子 -->
                <div class="rope" :class="{ropetop:isDown && layers == 1,ropeBtm:isDown && layers == 2}" :style="{'background-image': `url(${styleConfig.grab_rope})`}"></div>
                <!-- 钩子主体 -->
                <div class="hook" :style="{'background-image': `url(${styleConfig.grab_body})`}" ref="hookRef">
                    <!-- 钩手左 -->
                    <div class="hookLeft" :class="{downLeft:isDown}"  :style="{'background-image': `url(${styleConfig.grab_hand_left})`}"></div>
                    <!-- 钩手右 -->
                    <div class="hookRight" :class="{downRight:isDown}" :style="{'background-image': `url(${styleConfig.grab_head_right})`}"></div>
                </div>
            </div>
            <!-- 盲盒部分 -->
            <div class="blind-box">
                <!-- 上排盲盒 -->
                <div class="blind-top" :class="{'blind-top-stop': isStop}" ref="blindTop">
                    <div class="blind-item"  v-for="(item,index) in blindBoxTop" :key="index" :style="{'left': item.left + 'px'}">
                        <img :src="item.url" class="blind-img" alt="">
                    </div>
                </div>
                <!-- 下排盲盒 -->
                <div class="blind-btm" id="blind-btm" :class="{'blind-top-stop': isStop}" ref="blindBtm">
                    <div class="blind-item" v-for="(item,index) in blindBoxBtm" :key="index" :style="{'left': item.left + 'px'}">
                        <img :src="item.url" class="blind-img" alt="">
                    </div>
                </div>
            </div>
        </div>
        <!-- 抓抓乐按钮 -->
        <div class="grab-btn" :style="{'background-image': `url(${styleConfig.grab_btn})`}" @click="grabFn" v-log="'商店-抓抓乐抽奖按钮'">
            <!-- 抓抓乐按钮文案  -->
            <div class="grab-btn-text" :style="{'color': styleConfig.btn_text_color}">{{ styleConfig.btn_text }}</div>
        </div>
    </div>
</template>

  • script
<script>
export default {
   // 导出了btnClick抽奖按钮点击 and prizePopupFn 中奖弹窗
   props: {
       // 盲盒数组
       blindArray: {
           type: Array,
           default: ()=>{
               return []
           }
       },
       // 是否调用抽奖动画
       isAnimation: {
           type: Boolean,
           default: false
       },
       // 抓抓乐样式
       styleConfig: {
           type: Object,
           default: ()=>{
               return {
                   grab_bg: '', // 背景图
                   grab_rope: '', // 绳子图
                   grab_body: '', // 钩子主体
                   grab_hand_left: '', // 钩子左
                   grab_head_right: '', // 钩子右
                   grab_btn: '', // 按钮图片
                   btn_text_color: '', // 按钮文案颜色
                   btn_text: '', // 按钮文案
               }
           }
       }      
   },
   watch: {
       isAnimation(newVal) {
           if (newVal) {
               this.grabLottrey()
           }
       }, 
   },
   created () {
       // 适配
       const iphone6ItemWidth = 85;
       const curScreenWidth = window.screen.width;
       const curItemWidth = curScreenWidth / 375 * iphone6ItemWidth;
       let blind1 = {
           url: this.blindArray[0],
           left: 0,
       }
       let blind2 = {
           url: this.blindArray[1],
           left: curItemWidth,
       }
       let blind3 = {
           url: this.blindArray[2],
           left: curItemWidth * 2,
       }
       let blind4 = {
           url: this.blindArray[3],
           left: curItemWidth * 3,
       }
       let blind5 = {
           url: this.blindArray[4],
           left: curItemWidth * 4,
       }
       let blind6 = {
           url: this.blindArray[0],
           left: curItemWidth * 5,
       }
       let blind7 = {
           url: this.blindArray[1],
           left: curItemWidth * 6,
       }
       let blind8 = {
           url: this.blindArray[2],
           left: curItemWidth * 7,
       }
       let blind9 = {
           url: this.blindArray[3],
           left: curItemWidth * 8,
       }
       let blind10 = {
           url: this.blindArray[4],
           left: curItemWidth * 9,
       }
       this.blindBoxTop.push(blind1,blind2,blind3,blind4,blind5,blind6,blind7,blind8,blind9,blind10) // 上层盲盒
       this.blindBoxBtm = JSON.parse(JSON.stringify(this.blindBoxTop)) // 下排盲盒
   },
   data () {
       return {
           blindBoxTop: [], // 上排盲盒
           blindBoxBtm: [], // 下排盲盒
           isDown: 0, // 绳子拉长
           layers: 1, // 落下层数
           isStop: 0, // 是否暂停盲盒移动
           timeid: null,
           isAim: null
       }
   },
   methods: {
       // 点击抽盲盒
       grabFn() {
           this.$emit('btnClick') // 导出按钮点击事件 
       },
       // 钩子动画
       hookFn() {
           this.isDown = 1  // 钩子是否下落
           let cdnTop = this.$refs.blindTop.childNodes
           let cdnBtm = this.$refs.blindBtm.childNodes
           if (this.layers == 1) {
               this.timeid = setTimeout(()=>{
                   for (let i = 0; i < cdnTop.length; i++) {
                       console.log(cdnBtm[i].getBoundingClientRect().left)
                       if (cdnTop[i].getBoundingClientRect().left >= 100 && cdnTop[i].getBoundingClientRect().left < 220) {
                           let sltbox = this.$refs.hookRef.appendChild(cdnTop[i])
                           setTimeout(()=>{
                               this.initial() // 初始化盲盒移动
                               this.$refs.blindTop.appendChild(sltbox) // 盲盒复位
                           },2500)
                           clearTimeout(this.timeid)
                           return
                       } 
                   }
               },2000)
           } else if (this.layers == 2) {
               this.timeid = setTimeout(()=>{
                   for (let i = 0; i < cdnBtm.length; i++) {
                       console.log(cdnBtm[i].getBoundingClientRect().left)
                       if (cdnBtm[i].getBoundingClientRect().left >= 100 && cdnBtm[i].getBoundingClientRect().left < 220) {
                           let sltbox = this.$refs.hookRef.appendChild(cdnBtm[i])
                           setTimeout(()=>{
                               this.initial() // 初始化动画
                               this.$refs.blindBtm.appendChild(sltbox) // 盲盒复位
                           },3500)
                           clearTimeout(this.timeid)
                           return
                       }
                   }
               },3000)
           }
       },
       // 盲盒动画
       blindFn() {
           this.layers =  Math.ceil(Math.random()*2);
           this.hookFn() // 调用钩子动画
           if (this.layers == 1) {
               setTimeout(()=>{  // 如果是第一层点击后延时停止盲盒移动
                   this.isStop = 1
               },2000)
           } else if (this.layers == 2) {
               setTimeout(()=>{  // 如果是第二层点击后延时停止盲盒移动
                   this.isStop = 1
               },3000)
           }
       },
       // 盲盒抽奖
       grabLottrey() {
           this.blindFn() // 调用盲盒动画
           // 判断下落的层数控制弹中奖弹窗的延时时间
           if (this.layers == 1) {
               setTimeout(()=>{
                   this.$emit('prizePopupFn')
               },3000)
           } else if (this.layers == 2) {
               setTimeout(()=>{
                   this.$emit('prizePopupFn')
               },5000)
           }
       },
       // 初始化动画
       initial() {
           this.isStop = 0  // 盲盒移动
           this.isDown = 0 // 初始化下落类名
       },
   }
}
</script>


  • style
<style lang="scss" scoped>
@import '../css/mixin.scss';
    // 抓抓乐
    .shopping-grab {
        height: 1068px;
        width: 100%;
        // background-color: pink;
        margin-bottom: 20px;
        background-size: 100% 100%;
        background-repeat: no-repeat;
        padding-top: 66px;
        position: relative;
        box-sizing: border-box;
        .grab-box {
            height: 750px;
            width: 606px;
            // background-color: pink;
            overflow: hidden;
            margin: 0 auto 10px;
            padding-top: 280px;
            position: relative;
            // 钩子
            .hook-box {
                z-index: 99;
                position: absolute;
                top: 0;
                left: 50%;
                transform: translateX(-50%);
                // 被抓起来的盒子样式
                .blind-item {
                    margin: 0 auto;
                    width: 150px;
                    height: 236px;
                    overflow: hidden;
                    .blind-img {
                        height: 136px;
                        width: 150px;
                        margin: 90px auto 0;
                    }
                }
                // 绳子 infinite
                .ropetop {
                    animation: animationTop 4s linear;
                }
                .ropeBtm {
                    animation: animationBtm 6s linear;
                }
                .rope {
                    width: 16px;
                    height: 50px;
                    background-size: 100% 100%;
                    background-repeat: repeat;
                    margin: 0 auto 0;
                    transform: translate3d(0, 0, 0); 
                    // animation: animationBtm 7s linear;                       
                }
                @keyframes animationTop {
                    0% {
                        height: 50px;
                    }
                    50% {
                        height: 210px;
                    }
                    100% {
                        height: 50px;
                    }
                }
                @keyframes animationBtm {
                    0% {
                        height: 50px;
                    }
                    50% {
                        height: 470px;
                    }
                    100% {
                        height: 50px;
                    }
                }
                // 钩子部分
                .hook {
                    width: 170px;
                    height: 124px;
                    background-size: 100%;
                    background-repeat: no-repeat;
                    margin: 0 auto 0;
                    position: relative;
                    .hookLeft {
                        position: absolute;
                        left: 0;
                        top: 36%;
                        height: 100px;
                        width: 50px;
                        background-size: 100%;
                        background-repeat: no-repeat;
                    }
                    .downLeft {
                        transform: rotate(22deg);
                        transform-origin: 10px 10px;
                        transition: 1s;
                    }
                    .downRight {
                        transform: rotate(-22deg);
                        transform-origin: 40px 10px;
                        transition: 1s;
                    }
                    .hookRight {
                        position: absolute;
                        right: 0;
                        top: 36%;
                        height: 100px;
                        width: 50px;
                        background-size: 100%;
                        background-repeat: no-repeat;
                    }
                }
            }
            // 盲盒  
            .blind-box {
                height: 412px;
                width: 100%;
                position: relative;
                .blind-top {
                    animation: rolling1 7s linear infinite;
                    position: absolute;
                    height: 140px;
                    // display: flex;
                    width: 1700px;
                    // transform: translate3d(0, 0, 0);
                    /* Other transform properties here */
                    @keyframes rolling1 {
                        from {
                            transform: translate3d(0,0,0);
                        }
                        to {
                            transform: translate3d(-50%,0,0);
                        }
                    }
                    .blind-item {
                        position: absolute;
                        top: 0;
                        // margin-right: 20px;
                        width: 150px;
                        height: 136px;
                        .blind-img {
                            height: 136px;
                            width: 150px;
                            margin: 0 auto;
                        }
                    }
                    .blind-bmbox {
                        width: 1700px;
                        height: 140px;
                    }
                }
                .blind-btm {
                    animation: rolling2 7s linear infinite;
                    position: absolute;
                    top: 284px;
                    height: 140px;
                    // display: flex;
                    width: 1700px;
                    @keyframes rolling2 {
                        from {
                            transform: translate3d(-50%,0,0);
                        }
                        to {
                            transform: translate3d(0,0,0);
                        }
                    }
                    .blind-item {
                        // margin-right: 20px;
                        position: absolute;
                        top: 0;
                        width: 150px;
                        height: 136px;
                        .blind-img {
                            height: 136px;
                            width: 150px;
                            margin: 0 auto;
                        }
                    }
                    .blind-bmbox {
                        width: 1700px;
                        height: 140px;
                    }
                }
                .blind-top-stop {
                    animation-play-state:paused;
                }
            }
        }
        .grab-btn {
            position: absolute;
            bottom: 76px;
            left: 50%;
            transform: translateX(-50%);
            width: 420px;
            height: 160px;
            background-size: 100%;
            // background-color: #fff;
            background-repeat: no-repeat;
            margin: 0 auto 10px;
            .grab-btn-text {
                font-size: 22px;
                text-align: center;
                line-height: 170px;
            }
        }
    }
</style>

相关文章

  • Vue实现前端动画抓抓乐抽奖

    CSS3 动画卡顿性能优化 明确以下几个概念:单线程,主线程和合成线程,浏览器是单线程运行的(注意,是执行,并不是...

  • 乐高抓抓乐

    2017.4.2 今天我做了一个抓抓乐。它是这样做的。 1. 做机械爪:材料,轴3根,三个齿轮,圆梁2根,中齿轮3...

  • 乐抓抓技术支持

    技术支持联系方式:caopengju1992@qq.com

  • 抓鱼乐

  • Fiddler 抓包基本操作(实操篇)

    Fiddler: 抓包开关: 默认处于抓包状态,左下角点击capturing,可以禁止抓包。再次点击实现抓包的状态...

  • 哥抓鹅

    哥哥门前弯弯河,河里游着大白鹅,哥哥饿哥哥乐,哥哥跳进河里抓白鹅,鹅不让哥抓鹅,哥在河里抓白鹅,抓住鹅,哥哥心里乐...

  • 公文写作中的常用句式提炼汇总,拿走不谢!

    ✎1.“抓”字句 常见句型有: (1)抓××,在××××上实现新突破(措施目的)。 (2)抓××,在××××方面下...

  • 公文写作中的常用句式提炼汇总,拿走不谢!

    ✎1.“抓”字句 常见句型有: (1)抓××,在××××上实现新突破(措施目的)。 (2)抓××,在××××方面下...

  • 抓!抓!抓!

    一个女人,她向空中伸出张开的手,然后突然五指聚拢,向内握紧拳头……她好像是在奋力地抓某样东西。看她的表情,眼中放光...

  • 抓抓抓

    抓权,抓事

网友评论

      本文标题:Vue实现前端动画抓抓乐抽奖

      本文链接:https://www.haomeiwen.com/subject/lavzcrtx.html