精华内容
下载资源
问答
  • 优化动画卡顿:卡顿原因分析及优化方案
    千次阅读
    2020-09-15 21:48:57

    目录

    一、动画卡顿分析

    动画卡顿的原因

    大多数设备的刷新频率是60次/秒,也就是1秒钟的动画是由60个画面连在一起生成的,所以要求浏览器对每一帧画面的渲染工作要在16ms内完成。当渲染时间超出16ms时,1秒钟内少于60个画面生成,就会有不连贯、卡顿的感觉,影响用户体验。

    页面渲染流程

    一个页面帧在客户端的渲染分为以下几步:

    1. JavaScript:JavaScript实现动画效果,DOM操作等。
    2. Style(样式计算):确认每个DOM元素应用的CSS样式规则。
    3. Layout(布局):计算每个DOM元素最终在屏幕上的大小和位置。由于DOM元素的布局是相对的,所以当某个元素发生变化影响了布局时,其他元素也会随之变化,则需要回退重新渲染,这个过程称之为reflow。
    4. Paint(绘制):在多个层上绘制DOM元素的文字、颜色、图像、边框和阴影等。
    5. Composite(Render Layer合并):按照合理的顺序合并图层并显示到屏幕上。 浏览器在实际渲染页面的时候需要经过一系列的映射,由HTML页面构建出来的DOM树到最终的图层,映射过程如下图(来源:参考[3])所示(注意下图类名在后续有所更改,RenderObject->LayoutObject,RenderLayer->PaintLayer):
    • Node->RenderObject:DOM树的每个Node都有一个对应的RenderObject(一对一关系,RenderObject包含了Node的内容);

    • RenderObject -> RenderLayer:一个或多个RenderObject对应一个RenderLayer(多对一),RenderLayer用于保证元素之间的层级关系,一般来说位于同一位置的且层级相同的元素位于同一个Render Layer,只有某些特殊的RenderObject会专门创建一个新的渲染层,其他的RenderObject与第一个拥有RenderLayer的祖先元素共用一个。常见的生成RenderLayer的RenderObject拥有以下的一种特征参考[3]

      • 页面根元素
      • 有CSS定位属性(relative, absolute, fixed, sticky)
      • transparent不为1
      • overflow不为visible
      • 有CSS mask属性
      • 有CSS box-reflect属性
      • 有CSS filter属性
      • 3D或硬件加速的2D canvas元素
      • video元素
    • RenderLayer -> GraphicsLayer:一个或多个RenderLayer对应一个GraphicsLayer(多对一),某些被认为是Compositing Layer的RenderLayer单独对应一个GraphicsLayer,其他RenderLayer与第一个拥有GraphicsLayer的祖先元素共用一个GraphicsLayer。每个GraphicsLayer有一个GraphicsContext用于绘制其对应的RenderLayers,合成器将GraphicsContexts的位图合成,最终显示到屏幕上。渲染层提升为合成层的原因如下:

      • 有3D transform属性
      • 有perspective属性
      • 3D canvas或硬件加速的2D canvas
      • 硬件加速的iframe元素(如iframe嵌入的页面有合成层,合成层需要硬件加速)
      • 使用了硬件加速的插件,如flash
      • 对opacity/transform属性应用了animation/transition(当animation/transition为active)
      • 子元素是compositing layer
      • 兄弟元素是compositing layer,与当前的非composting layer有重叠,层级低于当前层
      • 有will-change属性

    二、优化方法

    在网上可以看到很多的优化方案总结,大佬们都写的很好。

    Talk is cheap. Show me the code.

    结合页面渲染流程,这里将结合一些测试代码,分析动画的各种优化方案和效果:

    • JavaScript:优化JavaScript的执行效率
      • requestAnimationFrame代替setTimeoutsetInterval
      • 可并行的DOM元素更新划分为多个小任务
      • DOM无关的耗时操作放到Web Workers
    • Style:降低样式计算复杂度和范围
      • 降低样式选择器的复杂度
      • 减少需要执行样式计算的元素个数
    • Layout:避免大规模、复杂的布局
      • 避免频繁改变布局
      • 用flexbox布局替代老的布局模型
      • 避免强制同步布局事件
    • Paint/Composite:GPU加速
      • 将移动或渐变元素由渲染层(RenderLayer)提升为合成层(Compositing Layer)
      • 避免提升合成层的陷阱

    JavaScript:优化JavaScript的执行效率

    1. requestAnimationFrame代替setTimeoutsetInterval

    为什么setTimeoutsetInterval不好?
    由于js是单线程执行,所以为了防止某个任务执行时间过长而导致进程阻塞,js中存在异步队列的概念,对于如setTimeoutajax请求都是把进程放到了异步队列中,当主进程为空时才执行异步队列中的任务。所以 setTimeoutsetInterval无法保证回调函数的执行时机,可能会在一帧之内执行多次导致多次页面渲染,浪费CPU资源甚至产生卡顿,或者是在一帧即将结束时执行导致重新渲染,出现掉帧的情况。
    requestAnimationFrame是怎么优化的?

    • CPU节能,当页面被隐藏或最小化时,暂停渲染。
    • 函数节流,其循环间隔是由屏幕刷新频率决定的,保证回调函数在屏幕的每一次刷新间隔中只执行一次。

    优化效果具体如何?DEMO
    通过chrome的performance面板查看具体表现的差别。
    通过setTimeout进行了3次渲染,而且有长时间帧出现:


    使用 requestAnimationFrameDOM操作部分合并,只进行了2次渲染,长时间帧也被优化:

    2. DOM无关的耗时操作放到Web Worker

    Web Worker的好处是什么?
    JavaScript是单线程的,如果频繁的进行耗时操作(如实时更新数据),就会造成拥堵,影响用户交互体验。Web Worker的作用在于为JavaScript创建了多线程环境,worker线程在后台运行,受主线程控制,两者互不干扰。worker线程负担高延迟且UI无关的任务,主线程负责UI交互就会相对流畅。
    需要注意

    • Web Worker无法操作DOM,本质上只是将数据刷新和页面渲染拆开执行。
    • Web Worker遵循同源策略且限制本地访问。
    • 用一次多余的网络请求和浏览器线程资源来换取高效执行。

    优化效果具体如何?DEMO
    可以通过chrome的performance面板查看具体表现的差别: 不使用web worker,减少了一次网络请求,但是出现了长时间帧,有卡帧的风险。


    使用了 web worker之后,耗时操作无关的任务不再被阻塞,但是增加了网络延迟。如果在项目中使用worker,初始化时间需要好好斟酌。

    可考虑的应用场景

    • 轮询服务器获取数据
    • 频繁的数据上报
    • 耗时的数据处理

    Style:降低样式计算复杂度和范围

    1. 降低样式选择器的复杂度?

    降低样式选择器的复杂度是常常被提出的一个优化方法,实际上这个方法的效果比较微弱,根据Ivan Curic的文章[5]的测试方法(DEMO),在一个拥有50000个节点的页面中,不同选择器复杂度对于性能的影响不会超过20ms,而一般情况下,页面的节点数都不会达到这个数量。
    优化效果微弱的原因在于浏览器引擎对选择器速度进行了优化,不同引擎的性能优化方案不同,所以开发者的优化是否有效是难以预测的,至少对于静态元素的优化性价比是极低的。
    通过测试可以确认的一点是,应当减少伪类选择器和过长的选择器的使用。推荐按照如OOCSS、BEM等命名规范来组织CSS,优点是在微弱优化性能的同时也提高了代码可维护性。

    2. 减少需要执行样式计算的元素个数

    这一点是针对较早的浏览器而言,较早的浏览器如改变了body元素上的一个类,则其子元素都需要重新计算样式。
    现代浏览器都进行了优化,所以优化效果要视具体应用场景而言。目前尚未挖掘到应用例子,后期如有发现回来填坑。

    Layout:避免大规模、复杂的布局

    1. 避免频繁触发布局

    不同的属性导致的渲染成本不尽相同,这一点在css动画时对比尤其明显。触发layout或者paint的动画属性尤其消耗性能,所以应当尽量使用transformopacity作为动画属性,如果无法实现则考虑采用JavaScript实现动画。
    性能差别有多大? 以width和transform为例,分别实现动画的性能差别:DEMO
    通过width实现动画,帧率较低且曲线抖动明显,右下角也给出了一帧的渲染过程,触发了样式计算,布局,绘制和渲染层合并:


    通过transform实现动画,可以发现帧率虽然也低但是平稳,渲染过程只触发了样式计算和、绘制和渲染层合并(仅当元素为合成层时,不会触发绘制。后面将详细讲述):

    2. 用flexbox布局替代老的布局模型

    常用的经典布局方案有基于浮动的布局、基于绝对定位的布局,flexbox布局相较而言更加高效。在能用flexbox布局的项目中,尽量用flexbox布局。以下DEMO尝试用三种布局方式渲染一样的界面效果来测试性能:
    绝对布局:对于每一个元素都需要唯一的定位坐标,当元素较多时,CSS文件偏大,导致在样式计算上花费了较多的时间。


    浮动布局:浮动元素之间定位会互相影响,部分浮动元素也受到文档流影响,导致布局所需时间较长。

    弹性布局:对比前两种布局方案而言,性能有较显著的提升。

    3. 避免强制同步布局事件

    什么是强制同步布局?
    前面提到了页面渲染流程是JavaScript->Style->Layout->Paint->Composite,强制同步布局就是强制浏览器在执行JavaScript脚本前先执行布局。
    什么情况会导致强制同步布局?
    JavaScript运行时,获取到的元素属性样式都是上一帧的数值,所以如果在当前帧的渲染流程中,获取当前帧的某个元素属性之前对该元素进行了修改,浏览器就必须先应用属性再执行JavaScript逻辑,简而言之就是DOM先写后读操作,尤其是连续的读写操作,对浏览器的性能影响更大。 对性能影响有多大?DEMO
    DEMO通过改变1000个节点的属性,测试强制同步布局事件对性能的影响,具体参照下图。可以发现性能的损耗是极大的,连续的读写操作导致连续的强制同步事件触发,JavaScript执行时间变得很长:

    Paint/Composite:GPU加速

    1. 将移动或渐变元素由渲染层(RenderLayer)提升为合成层(Compositing Layer)

    注:可在Chrome的开发者工具的layers面板查看合成层,layers面板打开方法command+shift+p(mac)/ctrl+shift+p(windows) -> show layers 将复杂/频繁变化的元素提升到合成层,这样的好处是该元素绘制的时候不会触发其他元素的绘制。渲染层提升为合成层的原因如下(注意以下原因是在渲染层的基础之上):

    • 有3D transform属性
    • 有perspective属性
    • 3D canvas或硬件加速的2D canvas
    • 硬件加速的iframe元素(如iframe嵌入的页面有合成层,合成层需要硬件加速)
    • 使用了硬件加速的插件,如flash/iframe
    • 对opacity/transform属性应用了animation/transition(当animation/transition为active)
    • will-change属性为opacity、transform、top、left、bottom、right
    • 子元素是compositing layer
    • 兄弟元素是compositing layer,与当前的非composting layer有重叠,composting layer的层级低于非composting layer层

    为什么会有性能提升?

    • 只重绘需要重绘的部分
    • GPU加速:合成层的位图直接由GPU合成,比CPU处理速度更快

    性能提升有多少? DEMO 通过demo可以看到,提升为合成层之后,paint所需的时间大大减少。

    提升合成层是不是越多越好?
    可以看到提升合成层后,paint时间大大下降。但是合成层的创建需要消耗额外的内存和管理资源,过多的合成层给页面带来的内存开销很大,DEMO创建了5000个元素,全部元素都提升为合成层与不提升时的内存消耗进行对比。这一点在移动端尤其需要注意,相比较于PC,移动设备的内存资源更加紧张。

    只提升动画元素的渲染层
    基于提升为合成层来提升性能的原理,当页面其他部分绘制比较复杂且相对静态时,我们可以考虑将动画元素单独提升为合成层,减少动画元素对页面其他元素的影响。

    2. 避免提升合成层的陷阱

    回顾一下提升为合成层的最后一个原因:兄弟元素是compositing layer,与当前的非composting layer有重叠,composting layer的层级低于非composting layer层。
    这种情况下导致的提升合成层一般都是预期外的。其原因与屏幕的渲染流程有关,我们回忆一下页面映射的最后一步,每一个Compositing Layer对应一张位图,合成器最后将这些位图根据层级关系合并起来最终输出到屏幕。此时我们假设A是已知的合成层,而B理想中应当是普通渲染层,其层级关系如图所示:


    B作为普通渲染层与父级元素位于同一张位图,A单独在一张位图,此时合并的时候层级就会出现问题,如果直接将B置于A之上,有可能导致层级低于A的B的父元素反而显示在了A之上,反之A,B的层级关系就不对了。浏览器此时的解决方案,就是将B也单独出来作为compositing layer进行渲染,导致了意料外的compositing layer生成。 这种时候第一直觉就是避免重叠的发生不就好了嘛?然而事情并不简单。在查找资料的时候发现了一个神奇宝贝—— assumedOverlap。字面意思是假设重叠,对于无法/难以判断是否会与compositing layer重合的某些元素,浏览器假设会发生重叠,提升为compositing layer。
    对此浏览器也进行了优化的,通过层压缩(Layer Squashing)处理,将与合成层有重叠且连续多个的渲染层合并为一个合成层。防止由于重叠导致的提升合成层过多,导致的层爆炸(Layer Explosion),可参考 DEMO
    然而层压缩还是有解决不了的情况,查看 源码可以列出以下原因(注意一下都是在重叠/假设重叠的前提下):

    • scrollsWithRespectToSquashingLayer:渲染层相对于压缩层滚动,当滚动的渲染层与合成层重叠时,会有新的合成层生成且无法压缩。DEMO(这个例子不是很好,codepen用iframe嵌入,整个iframe都变成了合成层,如果想看效果可以在本地看)
    • squashingSparsityExceeded:渲染层压缩后会导致压缩层过于稀疏。DEMO
    • squashingClippingContainerMismatch:渲染层和压缩层的裁剪容器(clip container)不同,简单理解就是重叠的渲染层的容器overflow类型不同。DEMO
    • squashingOpacityAncestorMismatch:渲染层与压缩层的继承自祖先的opacity属性不同。DEMO
    • squashingTransformAncestorMismatch:渲染层与压缩层的继承自祖先的transform不同。DEMO
    • squashingFilterAncestorMismatch:渲染层与压缩层的继承自祖先的filter属性不同,或者是渲染层本身有filter属性。DEMO
    • squashingWouldBreakPaintOrder:无法在不打乱渲染顺序的前提下压缩(e.g. 父元素有mask/filter属性,子元素与压缩层overlap,则假如合并了,父元素的mask/filter属性无法局部应用在压缩层,导致渲染结果有误)。DEMO
    • squashingVideoIsDisallowed:video元素无法被压缩。DEMO
    • squashedLayerClipsCompositingDescendants:当合成层是被剪切的子元素时,与之重叠的渲染层无法被压缩。DEMO
    • squashingLayoutPartIsDisallowed:无法压缩frame/iframe/plugin。
    • squashingReflectionDisallowed:无法压缩有reflection属性的渲染层。 DEMO
    • squashingBlendingDisallowed:无法压缩有blend mode属性的渲染层。DEMO
    • squashingNearestFixedPositionMismatch:渲染层的最近fixed元素与压缩层不同,无法被压缩。DEMO

    当发现页面明明没有什么内容却比较卡的时候可以检查一下是不是这个原因,以下给出常见的层压缩解决不了的情况:

    1. transform动画的元素,其后的元素为relative/absolute定位
      原因:relative元素和relative下的absolute元素由于assumedOverlap原因都被被提升为合成层,又由于设置了overflow:hidden,基于前面提到的squashingClippingContainerMismatch,渲染层与合成层的裁剪容器不同,导致无法层压缩,出现过多的合成层。 解决方法:为动画的元素设置z-index扰乱compositing layer的排序。DEMO

    三、参考

    本文结构主要参照文章[1],对其中的一些优化点进行了实际测试和扩展,也算是一篇读后感吧~
    关于层压缩部分情况过于复杂,没找到什么资料,感觉还没有完全吃透,后面有机会再重新整理一下。感恩以下大佬!

    1. 深度剖析浏览器渲染性能原理,你到底知道多少? www.jianshu.com/p/a32b890c2…
    2. Optimizing CSS: ID Selectors and Other Myths www.sitepoint.com/optimizing-…
    3. GPU Accelerated Compositing in Chrome www.chromium.org/developers/…
    4. GPU加速是什么 aotu.io/notes/2017/…
    5. Blink Compositing Update: Recap and Squashing docs.google.com/presentatio…
    6. 无线性能优化:Composite taobaofed.org/blog/2016/0…

    转载于:https://juejin.im/post/5c8a1db15188257e9044ec52



    更多相关内容
  • 如何实现切换页面时的过渡动画? 背景 今天在编写页面时,看到页面没有任何效果就只是直入直出,完全没有一点逼格,所以想要实现类似于原生app的那种切换页面时的特效,遂开始google,发现网上各种方案都是各有优缺点,于是...
  • 我们甚至为pi日设置了一种特殊模式,其中时钟以每小时100位的数字动画pi的数字。 时钟由网络连接的NodeMCU ESP8266供电,并使用Arduino语言和Arduino平台标准的流行C ++库进行编程。想要制作一个?附件将会告诉你...
  • 今天在编写页面时,看到页面没有任何效果就只是直入直出,完全没有一点逼格,所以想要实现类似于原生app的那种切换页面时的特效,遂开始google,发现网上各种方案都是各有优缺点,于是整理了自认为优雅的方案并记录下来. ...
  • 掌讯系统动画

    2019-03-10 14:53:53
    开机动画
  • Activity转场动画设置

    千次阅读 2019-01-18 19:56:07
    从一个Activity A跳转到另外一个Activity B,B页面从一个自定义的动画中出现,生活中常见的app都会自定义一个动画,也有的是手机里面定制好的原生动画,这里只谈加载B页面时的动画,A页面动画不理会 撸代码 一、先...

    在这里插入图片描述


    相关文章推荐
    android共享元素动画兼容低版本

    使用场景

    从一个Activity A跳转到另外一个Activity B,B页面关闭回到A。这里动画涉及两个activity,有以下过程:
    1.A打开B时A的离场动画和B的入场动画。
    2.B关闭回到A时B的离场动画和A的入场动画。

    原生自带

    Android已经内置了几种动画效果,可以见 android.R.anim 类。一般情况下我们需要自己定义屏幕切换的效果。

    自定义

    主要有以下四个属性:
    当我们从 A 启动 B 时,A 从屏幕上消失,这个动画叫做 android:activityOpenExitAnimation
    当我们从 A 启动 B 时,B 出现在屏幕上,这个动画叫做 android:activityOpenEnterAnimation
    当我们从 B 退出回到 A 时,B 从屏幕上消失,这个叫做 android:activityCloseExitAnimation
    当我们从 B 退出回到 A 时,A 出现在屏幕上,这个叫做 android:activityCloseEnterAnimation

    动画坐标定义

    在这里插入图片描述
    动画操作中操作的都是原点位置
    fromYDelta="100%"为底部
    fromYDelta="0%"为顶部
    fromXDelta="0%"为左侧
    fromXDelta="100%“为右侧
    fromXDelta=”-100%“为左侧超出一个屏幕宽度
    fromYDelta=”-100%"为顶部超出一个屏幕高度

    动画差值器

    动画差值器属性:
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    设置动画为先加速在减速(开始速度最快 逐渐减慢)
    android动画差值器详解

    方案选择

    有两种方案可以实现
    1.静态方式设置:将切换动画写到style中,再在theme中指定此style,在AndroidManifest中A和B的activity声明中设置此theme。
    2.动态方式设置:在A的startActivity()方法后调用:

    overridePendingTransition(R.anim.activity_b_translate_in_down_to_up,R.anim.activity_a_translate_out_down_to_up);
    

    来设置B的进入动画和A的消失动画。
    在B的finish()方法后调用:

    overridePendingTransition(R.anim.activity_a_translate_in_up_to_down,R.anim.activity_b_translate_out_up_to_down);
    

    来设置A的进场动画和B的消失动画。注意:要在super.finish()后设置。

    撸代码

    定义具体动画文件

    这里的动画可以是透明度、位移、缩放之类的任何动画,我这里以位移来举例。

    当我们 A 启动 B 时,我希望
    A 从下至上平移退出屏幕(activity_a_translate_out_down_to_up),
    B 从下至上平移进入屏幕(activity_b_translate_in_down_to_up)。

    当我们从 B 退出回到 A 时,我希望
    B 从上至下平移退出屏幕(activity_b_translate_out_up_to_down),
    A 从上至下平移进入屏幕(activity_a_translate_in_up_to_down)。

    1、activity_b_translate_in_down_to_up.xml

    <?xml version="1.0" encoding="utf-8"?>
    
     <translate xmlns:android="http://schemas.android.com/apk/res/android"
          android:duration="300"
          android:fromYDelta="100%"//以左上角顶点作为原点移动,Y100%即为顶部,X100%即为右侧,0%为底部
          android:interpolator="@android:anim/accelerate_decelerate_interpolator"
          android:toYDelta="0%">
     </translate>
    

    2、activity_b_translate_out_up_to_down.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <translate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="300"
        android:fromYDelta="0%"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:toYDelta="100%">
    </translate>
    

    3、activity_a_translate_in_up_to_down.xml

    <?xml version="1.0" encoding="utf-8"?>
    
     <translate xmlns:android="http://schemas.android.com/apk/res/android"
          android:duration="300"
          android:fromYDelta="-100%"
          android:interpolator="@android:anim/accelerate_decelerate_interpolator"
          android:toYDelta="0%">
     </translate>
    

    4、activity_a_translate_out_down_to_up.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <translate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="300"
        android:fromYDelta="0%"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:toYDelta="-100%">
    </translate>
    

    方案一

    一、定义切换动画 style

    res/values/styles.xml

     <style name="AnimFadeBottomToUp" parent="@android:style/Animation.Activity">
    
        <item name="android:activityOpenEnterAnimation">@anim/activity_b_translate_in_down_to_up</item>
        <item name="android:activityCloseExitAnimation">@anim/activity_b_translate_out_up_to_down</item>
        <item name="android:activityOpenExitAnimation">@anim/activity_a_translate_out_down_to_up</item>
        <item name="android:activityCloseEnterAnimation">@anim/activity_a_translate_in_up_to_down</item>
        
      </style>
    

    二、定义包含动画的 Activity 主题
    res/values/styles.xml
    很简单,就是使用 windowAnimationStyle 这个属性,指定切换动画的style即可。

     <style name="vertical_slide" parent="android:Theme.NoTitleBar">
        <item name="android:windowAnimationStyle">@style/AnimFadeBottomToUp</item>
      </style>
    

    三、注册该Activity到Manifest中,加上them属性就行了

     <activity
           android:name=".ActivityA"
           android:theme="@style/vertical_slide">
     </activity>
     <activity
           android:name=".ActivityB"
           android:theme="@style/vertical_slide">
     </activity>
    

    四、调用启动Activity

    	 A:
         Intent intent = new Intent(this, AnimationActivity.class);
         startActivity(intent);
         B:
         finish();
    

    方案二

    A:
    Intent intent = new Intent(v.getContext(), AnimationActivity.class);
    startActivity(intent);
    overridePendingTransition(R.anim.activity_b_translate_in_down_to_up,R.anim.activity_a_translate_out_down_to_up);
    
    B:
    @Override
    public void finish() {
        super.finish();
    	overridePendingTransition(R.anim.activity_a_translate_in_up_to_down,R.anim.activity_b_translate_out_up_to_down);
    }
    

    优缺点

    方案一
    优点:不需要考虑activity多个入口情况;
    缺点:系统兼容性不好,5.0以下实效

    方案二
    优点:无兼容性问题;
    缺点:多个入口的情况下每个入口都需要设置。

    转场动画实效问题集录

    1.在出现activity出现黑屏的时候 我们会这样设置
    在这里插入图片描述
    可以解决黑屏问题,同时引出另一个问题 :activity转场动画失效了

    检查下清单文件内的动画资源是不是引用的不对 是不是写出了这样
    在这里插入图片描述
    如果是这样的话 改成这样就可以啦
    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • Android系统定制,开机LOGO修改方案,开机动画修改方案,附件带有开发笔记,基于Android 5.1,高通平台msm8909定制。
  • 在 Vue 项目中,网站加载时会显示自定义动画(CSS),当文件加载完毕且页面显示时自动移除动画。 解决方案 原理:动画元素放置在 #app 内,Vue 生命周期开始时,会自动清掉 #app 下所有元素。 您可以打开控制台,...

    效果图

    在这里插入图片描述

    前言

    解决白屏等待方案非常多,个人而言评判好坏的标准即 用最少最简单的代码实现相同的功能。

    Vue 项目中,网站加载时会显示自定义动画(CSS),当文件加载完毕且页面显示时自动移除动画。

    解决方案

    😃 思路原理:动画元素放置在 #app 内,Vue 生命周期开始时,自动清除 #app 下所有元素,加载动画也随之消失。


    您可以打开控制台,将网速调慢(具体请百度)观察 <div id="app"></div> 节点显示隐藏变化,
    当页面处于加载状态时,<div id="app"></div> 节点存在,即加载动画执行,
    当页面加载完毕时,<div id="app"></div> 节点不存在,即加载动画消失。

    打开您的项目,找到根目录 public 文件夹下 index.html 文件,

    展开全文
  • //设置执行该函数的等待时间,如果上次执行时间和当前时间间隔大于16ms,则设置timeToCall=0立即执行,否则则取16-(currTime - lastFrameTime),确保16ms后执行动画更新的函数 var timeToCall = Math.max(0, 16 - ...

    在这里插入图片描述
    目前还有个问题:android上面无卡顿,但是ios直接把微信卡掉!
    wxml

    
    <canvas style='width:{{windowWidth}}px;height:{{height}}px;  position: fixed;' canvas-id="secondCanvas0">
    </canvas>
    <canvas style='width:{{windowWidth}}px;height:{{height}}px;  position: fixed;' canvas-id="secondCanvas1"></canvas>
    <view class='receivenow_view'>
      <view class="receivenow_button_view" bindtap='{{!start_state?"shuffle_func":"card_selection_func"}}' style='margin-top:{{height+10}}px' animation="{{animation3}}">
        <text>{{!start_state?"开始洗牌":"开始选牌"}}</text>
      </view>
    </view>
    
    

    wxss

    .receivenow_view {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      padding-bottom: 80rpx;
    }
    
    .receivenow_button_view {
      font-size: 30rpx;
      color: #fff;
      padding: 35rpx 190rpx;
      border-radius: 60rpx;
      background: linear-gradient(to right, #ff5846, #ff067a);
      line-height: normal;
    }
    

    js

    const animationFrame = require('../../utils/requestAnimationFrame.js')
    const ctx0 = wx.createCanvasContext('secondCanvas0')
    const ctx = wx.createCanvasContext('secondCanvas1')
    
    Page({
    
      /**
       * 
       */
      data: {
        //默认canvas高度
        height: 375,
        //默认canvas宽度
        windowWidth: 375,
        //背景资源
        bg: "",
        //卡片资源
        card: "",
        //是否开始洗牌
        start_state: false,
        //开场动画是否结束
        kaichang: false,
        // 是否开始选牌
        card_selection: false,
        //20张卡开场动画过后的所在位置x坐标
        xarrs: [],
        //20张卡开场动画过后的所在位置y坐标
        yarrs: [],
        //开场动画执行时间
        start_time: 1500,
        card_width: 46,
        card_height: 76.3
      },
      onLoad: function(options) {
        var that = this
        //获取手机屏幕宽度
        wx.getSystemInfo({
          success(res) {
            let windowWidth = res.windowWidth
            let height = windowWidth
            that.setData({
              height: height,
              windowWidth: windowWidth
            })
          }
        })
        // const ctx = wx.createCanvasContext('secondCanvas')
    
        // ctx.clearRect(0, 0, that.data.windowWidth, that.data.height)
        // ctx.draw()
    
        this.init();
      },
      //初始化数据,获取绘图所需图片
      init() {
    
        var doAnimationFrame = animationFrame.doAnimationFrame
        this.setData({
          bg: "/img/bg.jpg",
          card: "/img/card.png"
        })
        this.draw();
      },
      //开始画图    
      draw() {
        var that = this
        let width = that.data.windowWidth
        let height = that.data.height
        let nowtime = new Date().getTime()
        let time = that.data.start_time
        let card_width = that.data.card_width
        let card_height = that.data.card_height
        //用来存储所有卡片的x坐标和移动距离
        let xarrs = []
        //设置所有卡片的x坐标和移动距离
        for (let i = 0; i < 20; i++) {
          xarrs.push([width / 18, card_width * (i * 0.5)])
        }
        console.log(xarrs)
        //用来存储所有卡片的y坐标和移动距离
        let yarrs = [
          [height / 2 - card_height / 2, 0]
        ]
        //画一个背景
        ctx0.drawImage(that.data.bg, 0, 0, width, height);
    
        ctx0.draw()
        // animationFrame.doAnimationFrame,e为回调执行时间
        var rander = function(e) {
          e = e ? e : nowtime
          ctx.clearRect(0, 0, width, height) //清空所有的内容
          //绘制卡片  
          for (let i = 0; i < xarrs.length; i++) {
            ctx.drawImage(that.data.card, xarrs[i][0], yarrs[0][0], card_width, card_height);
            //从新设置卡片的x坐标和剩余移动距离
            xarrs[i] = that.move_x_func(xarrs[i][0], xarrs[i][1], time)
          }
          // console.log(arrs[0])      
          ctx.draw()
          //如果开始执行动画时间到最后一次的时间大于动画执行时间则停止动画
          if (e - nowtime < time) {
            var id = animationFrame.doAnimationFrame(rander);
          } else {
            //开场动画结束保存其位置
            that.setData({
              xarrs: xarrs,
              yarrs: yarrs,
              kaichang: true
            })
          }
        }
        rander()
      },
      //x坐标位置,以及移动距离(px),两秒移动s,16ms移动多少;time动画持续时间返回一个arr
      move_x_func(position, s, time) {
        // console.log(position)
        //动画持续时长两秒
        position = parseFloat(position.toFixed(2))
        //16ms移动的距离
        let time_distance = parseFloat((s * 16 / time).toFixed(2))
        s = parseFloat(s.toFixed(2))
        if (s === 0) {
          return [position, s];
        } else {
          return [position + time_distance, s - time_distance]
        }
      },
      //y坐标位置,以及移动距离
      move_y_func(position, s) {
    
      },
      //洗牌开始
      shuffle_func() {
        let that = this
        let width = that.data.windowWidth
        let height = that.data.height
        let nowtime = new Date().getTime()
        let time = that.data.start_time
        let card_width = that.data.card_width
        let card_height = that.data.card_height
        let xarrs = that.data.xarrs
        let yarrs = that.data.yarrs
        let time1 = 0
        //如果还未开场,不进行洗牌
        if (!that.data.kaichang | that.data.start_state) {
          return false;
        }
        var animation3 = wx.createAnimation({
          duration: 300,
          timingFunction: 'ease',
        })
        animation3.scale3d(0.1, 0.1, 0.1).step().scale3d(1, 1, 1).step();
        that.setData({
          animation3: animation3,
          //洗牌开始了,改变是否洗牌的状态
          start_state: true
        })
    
        let x = that.rnd(1, height / 2)
        let ys = []
        let xs = []
        let centers = []
        for (let i = 0; i < xarrs.length; i++) {
          ys.push(that.rnd(height / 10, height / 8))
          // xs.push(that.rnd(width / 8, width / 4))
          xs.push(width / 10)
          centers.push([that.rnd(width / 4, width / 2), that.rnd(height / 4, height / 2)])
        }
    
        //用户点击洗牌,执行另一个动画
        var rander = function(e) {
          ctx.clearRect(0, 0, width, height) //清空所有的内容 
          //设置中心点
          ctx.translate(width / 2, height / 2);
          for (let i = 0; i < xarrs.length; i++) {
            //设定每次旋转的度数
            // ctx.save()
            ctx.rotate(time1 * Math.PI / 540);
            ctx.drawImage(that.data.card, xs[i], ys[i], card_width, card_height);
            // ctx.restore()  
          }
          ctx.draw()
          time1++
          if (!that.data.card_selection) {
            var id = animationFrame.doAnimationFrame(rander);
          }
        }
        rander()
      },
      /**
       * 选牌开始
       * 所有当前卡牌归位
       */
      card_selection_func() {
        let that = this
        //设置开始选牌为true
        that.setData({
          card_selection: true
        })
      },
      //在min和max之间取随机
      rnd(min, max) {
        return min + Math.floor(Math.random() * (max - min + 1));
      },
    
      /**
       * 用户点击右上角分享
       */
      onShareAppMessage: function(options) {
        var that = this;
        return {
          title: "塔罗牌测试",
          path: '/pages/start/start',
          imageUrl: "/img/share.png",
          success: function(res) {
            var shareTickets = res.shareTickets;
            //如果分享不成功,或者不是到群
            if (shareTickets.length == 0) {
              return false;
            }
          }
        }
      },
    })
    

    requestAnimationFrame.js

    // 模拟 web端的requestAnimationFrame
    
    // lastFrameTime为上次更新时间
    var lastFrameTime = 0;
    var doAnimationFrame = function(callback) {
      //当前毫秒数
      var currTime = new Date().getTime();
      //设置执行该函数的等待时间,如果上次执行时间和当前时间间隔大于16ms,则设置timeToCall=0立即执行,否则则取16-(currTime - lastFrameTime),确保16ms后执行动画更新的函数
      var timeToCall = Math.max(0, 16 - (currTime - lastFrameTime));
      // console.log(timeToCall)
      var id = setTimeout(function() {
        callback(currTime + timeToCall);
        //确保每次动画执行时间间隔为16ms
      }, timeToCall);
      //timeToCall小于等于16ms,lastFrameTime为上次更新时间
      lastFrameTime = currTime + timeToCall;
      return id;
    };
    // 模拟 cancelAnimationFrame
    var abortAnimationFrame = function(id) {
      clearTimeout(id)
    }
    module.exports = {
      doAnimationFrame: doAnimationFrame,
      abortAnimationFrame: abortAnimationFrame
    }
    
    展开全文
  • Android Studio开发APP启动程序时开屏简单动画效果快速有效解决方案 大家在设计APP的末期,都会想给APP搞一些“花里胡哨”的特效来提高APP的B格。博主表示亲测有效的方式你值得拥有,下面是具体步骤: 一、在你想...
  • android 帧动画的替代方案

    千次阅读 2016-01-11 15:16:26
    在android 里有丰富的动画实现过程,其中帧动画的使用也是非常广泛,比如在网络加载数据的时候我们希望界面中间显示一头老牛努力跑动的效果. 此时我们大多都会选择让美工做好一系列老牛跑动的图片,少则5张,多则10张,...
  • 基于动画分镜设计课程下的多元化作业体系设置
  • 一、企业宣传片与3D动画制作方案 随着社会进步,虚拟动画技术的不断发展走向成熟,3D动画宣传片逐渐成为各大企业最为具有实战常用的一种宣传方式,是一个能够反映真实世界的概念,三维动画有许多制作方式,比如运用C...
  • Android帧动画无效解决方案

    千次阅读 2020-07-10 17:09:50
    两行代码解决帧动画无效的问题。
  • Activity的进出动画比较简单,但是实际开发的时候遇到了一点小问题,记录一下,因各厂商的原因,导致设置进出动画无效;
  • 这里在烘培好导入UE4后发现这个包没有动画,排查后发现之前烘培测试的时候只烘培了这个包的动画,所以之前烘培的关键帧与现在的关键帧发生冲突,解决方案就是直接删除之前烘培的关键帧即可。 ...
  • 由于在Next 6.0后设置 Canvas_nest: true是背景将会有动画效果,但是没有反应,查看一下官方文档有解释说明。Canvas_nest。 #解决方案 打开Git进入自己文件夹下/themes/next文件夹下 $ cd themes/next 运行...
  • Unity做动画不播放解决方案之一

    千次阅读 2020-08-27 17:17:17
    然后就出事了,没错就是这个动画,搞了半天,整个界面全部重做都试过,没解决。。。。。到约会的点了,太绝望了。想想还是约会比较重要,掐着点出去两小时,看了个电影立马赶回来了。。。哭泣,, 但是回来之后好像...
  • 基于Spine动画的AVATAR换装系统优化

    千次阅读 2021-12-15 21:07:49
    大家好,我是红孩儿,目前在... 很多游戏开发团队都正在使用Spine动画软件来制作人物AVATAR动画,作为一款广泛应用于游戏中的2D动画制作软件,Spine提供了良好的骨骼动画功能和库接口来实现人物AVATAR动画。 自...
  • 设置动画结束的监听事件

    千次阅读 2017-02-06 16:08:28
    于是就找解决方案,很显然最好的方案就是监听动画结束 解决办法:主要利用SetAnimationLisener 给imageview的准备setAnimation的那个动画设置一个SetAnimationLisener,然后导入,在onEnd里面去处理跳转等后续...
  • 示例二、常见错误及解决方案(1)@keyframes 不能实现突变的状态变化display替代方案1.占据空间:visiblity2.不想占据空间:绝对定位+visiblity3.消失前占据空间但是消失后不占据空间:timeout和visiblity(2)@...
  • Android【平移动画

    千次阅读 2022-01-02 17:45:04
    平移动画???? 1、具体操作步骤 创建ImageView对象 创建ObjectAnimator对象 通过ofFloat方法实现平移 2、具体实施 1、创建ImageView <ImageView android:id="@+id/car" android:layout_width="wrap_content" ...
  • android 设置全局的页面切换动画问题

    千次阅读 2015-12-04 18:39:31
    这两天在看android 设置页面切换动画,看的很纠结,晕菜了  我这里有四个手机,  一加 版本 5.1.1 小米2s 版本 5.0  华为P6 版本 4.4.2 酷派版本 4.2.2  一加和小米的效果是一样的,算正常,其他两个都不太相同...
  • 从网上下载的动画模型中,如果其自带的动画没有设置循环,在Unity中如何保证其能自动循环播放,条件是不修改“Rig->Anitmation Type->Legacy”;因为需要使用Avatar设置骨骼和AnimatorController设置运动状态...
  • slide及其里面的元素都可以使用swiper动画,因为初始swiper动画是根据onSlideChangeEnd(),即slide发生的切换变化,如果初始swiper时,设置noSwiper:true;则动画不能出现,因为swiper没有切换变化。 此时采取第...
  • Fresco gif动画不能设置圆形,为了看到圆形效果,有两种解决方案。1.通过BaseBitmapDataSubscriber获取gif第一帧的图像bitmap,显而易见,这个最终应该可以看到圆形,只是没有了动画效果。我没有试过,具体看这篇...
  • 这段时间看WPF3D,给3D加了给动画后,想加个按钮Reset,可一直没有效果,代码跑过去,值根本就没变 应该是值被锁了,不让改了,自己弄了很久还是没结果,还是网上查资料,...1.将动画属性FillBehavior设置为Stop,但这
  • 为什么写下这篇博文呢,因为Lottie是最近使用率极高的一个动画方案,他可以做很多css不好做又复杂的效果,笔者惊叹于现如今前端的发展,也是越用越香,抽空做个小笔记,有兴趣的同学也可以试试哦~ 什么是Lottie 先上...
  • unity导入FBX自动设置动画分段

    千次阅读 2018-03-07 15:28:54
     if (EditorUtility.DisplayDialog("模型导入", "是否使用预设方案", "是", "否"))  {  List<FbxModel> fbxModelList = new List();  string fileAnim = DragAndDrop.paths[0];  string text = Path....
  • ANSYS FLUENT瞬态模拟动画制作

    千次阅读 2021-06-04 11:18:58
    在初始化流场之前要进行的动画设置操作: 设置动画面 模型树→Solution→Calculation Activities→Solution Animations(动画) 改变名称——liquid_temperature New Object→Contour→Contour name改为l

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 72,311
精华内容 28,924
关键字:

动画方案怎么设置