热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Web仿App动画竟然引出了“性能杀手”

本文作者:杨晔原创声明:本文为阅文前端团队YFE成员出品,请尊重原创,转载请联系公众号(id:yuewen_YFE)获取授权,并注明作者、出处和链接。在我参与开发的对话小说项目过程中,我们发现创意类的活动对拉升转化数据很有帮助。经过调研,这款对话式小说产品的用户群体大多数都是比较年轻的90-95后,所以最后结论是希望以目前业界年轻化APP流行的交互形式——《滑卡片》对推书活动做一次改版,也同时希望

本文作者:杨晔

原创声明:本文为阅文前端团队 YFE 成员出品,请尊重原创,转载请联系公众号 ( id: yuewen_YFE ) 获取授权,并注明作者、出处和链接。

背景

在我参与开发的对话小说项目过程中,我们发现创意类的活动对拉升转化数据很有帮助。经过调研,这款对话式小说产品的用户群体大多数都是比较年轻的 90-95 后,所以最后结论是希望以目前业界年轻化 APP 流行的交互形式 —— 《滑卡片》对推书活动做一次改版,也同时希望这个页面能和产品本身结合作为一个常驻功能页,我们先来看一下最终的实现效果:

Web 仿 App 动画竟然引出了“性能杀手”

是不是挺流畅?接下来我会按照当时开发的思路和过程来讲述开发中经历了什么。

参考

在极为用心的设计师交付设计稿后,她还特地使用flinto :arrow_heading_down:做了交互原型来辅助我达到策划预期的效果。

Web 仿 App 动画竟然引出了“性能杀手”
Web 仿 App 动画竟然引出了“性能杀手”

《flinto 交互稿》

见到这份贴心的交互稿后,我首先想到的就是先去参考即刻 App 中的探索页,以及交友软件《探探》的交互形式,他们的交互效果分别如下:

Web 仿 App 动画竟然引出了“性能杀手”

《探探 App 》

Web 仿 App 动画竟然引出了“性能杀手”

《即刻 App - 探索》

两者效果非常相似吧?:smirk:但和我这次需求不同的是:我们的页面是内嵌在起点读书 App 内的 H5,而以上两者皆是由原生 App 开发实现的效果,所以我对“能否高度还原”以及”如何保证良好的性能”还是产生了一点担忧 。

尝试

样式重构思路在获取真实数据和开发复杂逻辑之前,我先用草图整理了一下实现思路:

Web 仿 App 动画竟然引出了“性能杀手”

如图所示:

  • 初始状态为3张卡片叠在一起,要有 3D 立体感,在拖拽的时候能露出后面两张
  • 拖拽第一张时卡片需要跟随手指滑动方向,超过一定距离放开手指后卡片飞出,后面的卡片自动往前推进一张,页面中始终需要 3 张卡片可见状态。

根据以上思路,既然要有 3D 立体感和推进动效,如果单独使用 z-index 来实现肯定不能满足,所以我选择使用 translateZ 来搭配完成这个堆叠卡片的推进效果,因为他能更好的显示出三维空间景深。如此一来,卡片往前推进和被扔出的卡片自动飞出等动效都可以完全交给 CSS3 动画过渡来完成。

样式代码(主要结构属性)

.card_container {
  position: relative;
  width: 6.86rem;
  height: 8.96rem;
  perspective: 1000px;
  perspective-origin: 50% 150%;
  -webkit-perspective: 1000px;
  -webkit-perspective-origin: 50% 150%;
}
.card {
  transform-style: preserve-3d;
  width: 100%;
  height: 100%;
  position: absolute;
  opacity: 0;

}

堆叠的卡片需要有一个父容器,让所有堆叠的卡片产生 3D 透视效果。

HTML 和绑定方法

我们还需要一些关键变量来记录一些可能实时变化的属性:

// 当前展示的图片index
currentIndex: 0,
// 记录偏移量
displacement: {
  x: 0,
  y: 0
},
// 位置信息
position: {
  start: { x: 0, y: 0 },
  end: { x: 0, y: 0 },
  direction: 1, // 滑动方向,左是-1,右是1
  swipping: false // 是否在拖动交换过程中
},
// 记录每一个丢出去的方向
directionArr: [],
// 显示图片的堆叠数量
visible: 3,
// 视口宽度
winWidth: 0,
//  滑动阈值
slideWidth: 70,
// 超过阈值时的自动偏移量
offsetWidth: 120,

再给 style 绑上 2 个初始化的方法。 cardTransform 用来初始化每张卡片的样式,indexTransform 用来初始化第一张卡片的样式。

// 初始化每张卡片的样式
cardTransform (index) {
    let style = {}
    //卡片自动位移距离(飞出屏幕多远)
    let offset = 0
    if (this.directionArr[index] === 1) {
      offset = 800
    } else if (this.directionArr[index] === -1) {
      offset = -800
    }
    
    style['z-index'] = this.currentIndex - index + this.visible 
    style['transform'] = `translate3d(0,0,${(this.currentIndex - index) * 60}px)`

  //让藏在后面的卡片缩小样式堆叠在一起并透明不显示。一旦飞走一张,下一张卡片会自动过渡动画往前推进
  if (index - this.currentIndex <0) {
    style['opacity'] = 0
    style['transform'] = `translate3d(${this.position.end.x + offset}px,${this.position.end.y}px,${(this.currentIndex - index) * 60}px) rotate(${this.position.direction * -65}deg)`
  }

  // 非手势滑动状态才添加过渡动画
  if (!this.position.swipping) {
    style['transitionTimingFunction'] = 'ease'
    style['transitionDuration'] = 300 + 'ms'
  }
  return style
},
// 第一张卡片的样式
indexTransform (index) {
  let style = {}
  if (index === this.currentIndex) {
    style['transform'] = `translate3d(${this.displacement.x}px,${this.displacement.y}px,${(this.currentIndex - index) * 60}px) rotate(${this.displacement.x / this.winWidth * -65}deg)`
  }
  // 非手势滑动状态才添加过渡动画
  if (!this.position.swipping) {
    style['transitionTimingFunction'] = 'ease'
    style['transitionDuration'] = 300 + 'ms'
  }

  return style

},

之后的拖拽卡片 touch 事件就相当于以前写拖拽 DIV 那样简单容易,返回上一张和背景过渡等细节的方法这里就不再做过多的代码展示了。

到此为止,使用了四本数的 mock 数据,一切都很顺利,动画也非常流畅:

Web 仿 App 动画竟然引出了“性能杀手”

App Webview crash :scream:

接着我开始请求真实数据,并做了一系列的优化,比如:

  1. 全机型适配卡片屏幕居中。
  2. 记录用户操作,拖拽扔出时的方向存入 localStorage (用户再次打开时看到的第一张卡片依然是之前离开时的,体验更像是在App内)
  3. 优化减少请求,首次进页面时加载 2 张图片,之后每飞走一张卡片时加载下一张图片。

优化之后,在 PC Chrome 移动端模式下一切看起来都是那么顺利,我自以为不会有什么问题,最后发布到测试环境用 App 扫码打开后看到的却是这一幕:

我一开始对性能的担忧终于还是发生了,App 内直接发生了崩溃,我再尝试用移动端浏览器打开,并没有发生崩溃,但是操作起来很不流畅,再回到 PC 上体验了一次,依然感知不到有什么卡顿,我想可能是由于手机硬件不如 PC, 发生崩溃的原因可能是 3D 渲染或者性能方面出现了问题。根据这个思路,我打算从数据上进行一次对比查看导致崩溃的关键要素是什么。

性能对比

首先使用 Chrome 自带的 Performance 进行了长达 7 秒的页面录制,在 7 秒钟我疯狂的对卡片操作了一番,最后得出的性能图如下:

Web 仿 App 动画竟然引出了“性能杀手”

除了有一个小警告:Handler took 之外并证明不了什么严重的问题。 我打算再监控一下渲染性能,我从 Chrome 的更多 工具 里调起了 Rendering 面板

Web 仿 App 动画竟然引出了“性能杀手”

在所有的选项全部打上勾后,造成问题的原因一下子就暴露了!

Web 仿 App 动画竟然引出了“性能杀手”

OMG :scream:,帧率只有 18 fps,而且原来所有的卡片都重合在了一起并进行了渲染。我马上意识到开发中的错误点: 那些隐藏的卡片虽然 把透明度设置为了 0,但看不见并不代表不会被渲染,那些被隐藏的卡片在每一次卡片飞出动画后都在实时被渲染推进动画,严重损耗了性能。

也就是说,opacity 造成了页面的大量 reflow,这时我才想起, opacity 和 visibility 都会造成回流,而只要有 reflow 必定会造成 repaint ,只有 display:none 可以避雷,因为它彻底脱离了文档流,在开发这个需求以来,我一直在优化页面还原度和动效,却忘记了这重要的一点。

优化

知道了问题的关键就好办多了,opacity 依然要保留,因为推进动效的过渡需要透明度来美化,光用 display 会变得非常生硬。既然用的是 VUE,那就更好办了,首先给数据中的数组全部添加上 display 属性,默认为 false,然后给 card 元素绑上了  :class="{display:item.display}",再将 css 的 card 样式全部设置为 display:none

在需要显示的时候让它变为 true,随即样式变为 block 。

.card.display {
  display: block;
  opacity: 1;
}

举个例子,比如我在 touchEnd 时有一个卡片移动的方法 moveNext。

touchEnd () {
  this.position.swipping = false
  this.position.end['x'] = this.displacement.x
  this.position.end['y'] = this.displacement.y

  // 判断滑动距离超过设定值时,自动飞出
  if (this.displacement.x > this.slideWidth) {
    this.moveNext(1) //往右
  } else (this.displacement.x <-this.slideWidth) {
    this.moveNext(-1)  //往左
  } 
  this.$nextTick(() => {
    this.displacement.x = 0
    this.displacement.y = 0
    this.isDrag = false
  })
}

我们就可以在 moveNext 时对 index 进行操作。moveNext 中需要对当前显示的第一张卡片和后面堆叠的都添加显示,已经消失的卡片变为隐藏,如此循环无缝衔接。另外,由于数据是不确定的,为避免某些极端情况(例如 首张卡片 再往前或者 最后倒数几张 后都会出现没有更多卡片的情况,所以还需要做细节容错处理)。

moveNext (direction) {
  this.position.direction = direction

  // 防止在最后倒数几张时操时出错
  try {
    this.dataArr[this.currentIndex + 3].display = true
  } catch (e) {

  }

  // 防止在第一张时操作出错
  if (this.currentIndex > 0) {
    try {
      this.dataArr[this.currentIndex - 1].display = false
    } catch (e) {

    }
  }
  
  this.currentIndex++ //每次让下一张卡片往前推进,反之 -- 就是返回上一张
  !direction ? this.position.end['x'] -= this.offsetWidth : this.position.end['x'] += this.offsetWidth
  this.position.end['y'] += this.offsetWidth / 2

},

在一番调整优化后,我重新调起了 Rendering 面板查看结果:

Web 仿 App 动画竟然引出了“性能杀手”

和预想的一样,帧数达到正常的 60 fps,不管如何操作,始终只有 3 张卡片是可见(被渲染的),性能得到了大大提升,重新回到 App 中访问也没有再遇到崩溃的问题。

扫码体验(使用起点 APP 查看效果更佳)

Web 仿 App 动画竟然引出了“性能杀手”

总结

经过这次 App webview 引起的崩溃事件,我从中吸取到了一些经验和总结,也希望对阅读此文章的你有所帮助 :blush:。

  1. 用 Web 模拟 App 原生动画时,特别是在移动端,使用高阶属性去实时动态地改变元素时需要特别谨慎。
  2. “肉眼感知”并不准确,也不能作为衡量依据,一切要以开发工具中的性能数据为基准来证明。
  3. reflow 和 repaint 在 PC 端只要不是怀有明知山有虎,偏向虎山行的心态去写代码,几乎不会引发性能问题,但是移动端的渲染能力和 PC 端差了一大截,一个不小心,由 CSS 引发 reflow 和 repaint 就会成为移动端的“性能杀手”。所以,在完成需求和动效前,对自己的方案提前进行一次性能的心理预期也是很有必要的,在考量页面性能的时候分析 reflow 和 repaint 也算是一个切入点。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 我们


推荐阅读
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 本文介绍了响应式页面的概念和实现方式,包括针对不同终端制作特定页面和制作一个页面适应不同终端的显示。分析了两种实现方式的优缺点,提出了选择方案的建议。同时,对于响应式页面的需求和背景进行了讨论,解释了为什么需要响应式页面。 ... [详细]
  • 本文整理了常用的CSS属性及用法,包括背景属性、边框属性、尺寸属性、可伸缩框属性、字体属性和文本属性等,方便开发者查阅和使用。 ... [详细]
  • CSS|网格-行-结束属性原文:https://www.gee ... [详细]
  •  项目地址https:github.comffmydreamWiCar界面做的很难看,美工方面实在不在行。重点是按钮触摸事件的处理,这里搬了RepeatListener项目代码,例 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了css回到顶部按钮相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 关于extjs开发实战pdf的信息
    本文目录一览:1、extjs实用开发指南2、本 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 本文介绍了Sencha Touch的学习使用心得,主要包括搭建项目框架的过程。作者强调了使用MVC模式的重要性,并提供了一个干净的引用示例。文章还介绍了Index.html页面的作用,以及如何通过链接样式表来改变全局风格。 ... [详细]
  • 本文介绍了一种图片处理应用,通过固定容器来实现缩略图的功能。该方法可以实现等比例缩略、扩容填充和裁剪等操作。详细的实现步骤和代码示例在正文中给出。 ... [详细]
  • 本文介绍了Cocos2dx学习笔记中的更新函数scheduleUpdate、进度计时器CCProgressTo和滚动视图CCScrollView的用法。详细介绍了scheduleUpdate函数的作用和使用方法,以及schedule函数的区别。同时,还提供了相关的代码示例。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有