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


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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容