感谢参考原文-http://bjbsair.com/2020-03-27...
通过一个月的时间,在我遇到了不少“这个我不会作啊?”,“这个到底怎么作“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各类问题以及解决方案。css
JDR DESIGN 是京东零售设计中台的门户站点,展现京东零售设计服务平台的产品以及应用场景,特色是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。html
JDR DESIGN 地址:https://jdrd.jd.com/前端
这是我入职以来负责的第一个项目,须要花大量时间来熟悉新的开发流程,项目排期很是紧凑,而且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,天天高强度的加班,回想起来虽然很难,可是很是有挑战性,很是有收获。webpack
做为一个 9012 年的 PC 端项目,咱们天然也须要很是先进的技术选型来帮助咱们提高研发生产力,因此一个优秀的前端框架和一个高效的前端工程化工具,天然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,知足了 JDRD 须要兼容IE10的要求。css3
自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工做,帮助咱们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。web
在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,总体架构设计以下:npm
Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:json
整个开发流程总结以下图所示:canvas
既然是总结,开发过程当中的流程固然没有这么完整,漏掉了一部分(已红色标注),致使开发到后面由于前面漏掉的环节,浪费了不少的时间。segmentfault
数据埋点:交互稿其实有详细介绍哪些地方须要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。
除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。
性能优化的初衷就是加快网站的加载速度,让用户可以更快的看到内容,上面介绍到前端工程化工具 Athena 已经作到合并、压缩了静态资源文件,那还有什么方法可以缩小请求的静态资源体积,加快首屏的加载速度呢,咱们尝试了如下性能优化手段。
楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。
Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你能够把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代码在首次加载时,会先展现一个 loadingBlock,而后动态加载 news_banner 的代码,组件代码加载完毕以后,便会替换掉 loadingBlock。Lazyload 经过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。须要注意的是 lazyload 须要设置高度,才会撑起懒加载的区域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首页为例,有四处组件是不须要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只须要加载首屏的头部、视频、banner 便可。切分以后,首屏 js 体积缩减了 50KB。
整站图片很是多,为了保持清晰度并且所有采用二倍图引入,消耗资源比较大,为了加快加载速度,咱们选择让滚动条滚动到图片的可视区后才加载该图片。
使用 Lazyload 实现,和上述组件懒加载介绍的同样,包裹着须要懒加载的图片,就能够实现图片懒加载。
图片懒加载以外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不一样状态展现图片的内容。
使用 Lazyimg 实现这个功能:
除了上面两点外,还能够从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件能够将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减少 chunks 包的大小。
以往的传统网站通常会在加载中展现一个 loading 态,也能够达到占位的效果,可是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽可能缩小真实模块结构与加载占位之间的视觉差别。
骨架屏的两个用途:
使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提早占位,占位符设置为骨架屏。
设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里若是 loaded === false ,则显示骨架屏。
骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不一样模块引入对应的骨架屏组件,这种方式和 loading 动画同样,耦合度低,可是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每一个页面结构不同,对应的骨架屏也是彻底不一样,骨架屏暂时不能抽成公共组件全局通用。
首页定稿设定的宽度为 1240px ,对小屏不够友好,咱们增长了一版窄版样式兼容小屏。
宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范须要和设计师统一规范,兼容窄版的开发就会变得很是简单。
只须要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新视图 isWide: evt.detail.isWide }); }); }
为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统作法是 css3 实现,这须要逐帧写动画细节,工做量很是大,咱们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现可以彻底还原AE动画。
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序能够像使用静态图像同样轻松地使用动画。这样实现起来就很是简单了。分如下两步:
lottie-web 文档中的方法很是全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操做。
项目示例代码以下:
npm install lottie-web //安装lottie-web import lottie from 'lottie-web' //引入lottie-web到项目中 //lottie-web经常使用方法 this.anim = lottie.loadAnimation({ //加载动画 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定帧区间 this.anim.setDirection(-1);//动画反向播放 this.anim.play();//播放动画 this.anim.pause()//暂停动画
通过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。
这里有 2 个须要注意的点:
以上就是用 Lottie 实现的动画,看到这里,是否是以为 so easy,可是 Lottie 并非万能的,不能解析全部的动画特性,开发前须要先看下支持列表。并和设计师确认是否都支持。
JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就能够作到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现以下:
// 在距离底部200px时,加载楼层组件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距离底部-200px时,加载入场动画组件,这时由于楼层组件已经加载过了,页面显示是真实组件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div class> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*计算每一个元素的 animation-delay */ } }
产品页的头部动效分两部分,氛围动效 + 波浪动效。
氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,咱们能够拿到须要自定义样式的标签的 id,自定义样式,如图所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定义样式的时候,这里有个坑必定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前尚未找到 ID 值 + 1 的缘由,可是为了让自定义的样式生效,须要给 ID 为 100 和 ID 为 101 的标签都加上样式。
经过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样咱们就能够经过配置参数获得想要的正弦波浪,具体实现以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas宽度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//动画曲线 waves: [//须要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//线条宽度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 "strokeStyle":"rgba(221,221,233,1)",//颜色 "type": function(x, waves) {//自定义波浪类型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋转角度 wavesWidth: '400%',//波浪宽度 });
根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
开发过程当中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 }
最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,作了如下工做:
本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程当中记录和总结,是收获满满的喜悦,也发现了一些能够优化的模块,让下次可以作得更好,在开发过程当中的多些思考和探究,最优化的设计项目。感谢参考原文-http://bjbsair.com/2020-03-27...
通过一个月的时间,在我遇到了不少“这个我不会作啊?”,“这个到底怎么作“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各类问题以及解决方案。
JDR DESIGN 是京东零售设计中台的门户站点,展现京东零售设计服务平台的产品以及应用场景,特色是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。
JDR DESIGN 地址:https://jdrd.jd.com/
这是我入职以来负责的第一个项目,须要花大量时间来熟悉新的开发流程,项目排期很是紧凑,而且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,天天高强度的加班,回想起来虽然很难,可是很是有挑战性,很是有收获。
做为一个 9012 年的 PC 端项目,咱们天然也须要很是先进的技术选型来帮助咱们提高研发生产力,因此一个优秀的前端框架和一个高效的前端工程化工具,天然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,知足了 JDRD 须要兼容IE10的要求。
自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工做,帮助咱们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。
在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,总体架构设计以下:
Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:
整个开发流程总结以下图所示:
既然是总结,开发过程当中的流程固然没有这么完整,漏掉了一部分(已红色标注),致使开发到后面由于前面漏掉的环节,浪费了不少的时间。
数据埋点:交互稿其实有详细介绍哪些地方须要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。
除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。
性能优化的初衷就是加快网站的加载速度,让用户可以更快的看到内容,上面介绍到前端工程化工具 Athena 已经作到合并、压缩了静态资源文件,那还有什么方法可以缩小请求的静态资源体积,加快首屏的加载速度呢,咱们尝试了如下性能优化手段。
楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。
Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你能够把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代码在首次加载时,会先展现一个 loadingBlock,而后动态加载 news_banner 的代码,组件代码加载完毕以后,便会替换掉 loadingBlock。Lazyload 经过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。须要注意的是 lazyload 须要设置高度,才会撑起懒加载的区域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首页为例,有四处组件是不须要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只须要加载首屏的头部、视频、banner 便可。切分以后,首屏 js 体积缩减了 50KB。
整站图片很是多,为了保持清晰度并且所有采用二倍图引入,消耗资源比较大,为了加快加载速度,咱们选择让滚动条滚动到图片的可视区后才加载该图片。
使用 Lazyload 实现,和上述组件懒加载介绍的同样,包裹着须要懒加载的图片,就能够实现图片懒加载。
图片懒加载以外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不一样状态展现图片的内容。
使用 Lazyimg 实现这个功能:
除了上面两点外,还能够从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件能够将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减少 chunks 包的大小。
以往的传统网站通常会在加载中展现一个 loading 态,也能够达到占位的效果,可是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽可能缩小真实模块结构与加载占位之间的视觉差别。
骨架屏的两个用途:
使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提早占位,占位符设置为骨架屏。
设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里若是 loaded === false ,则显示骨架屏。
骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不一样模块引入对应的骨架屏组件,这种方式和 loading 动画同样,耦合度低,可是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每一个页面结构不同,对应的骨架屏也是彻底不一样,骨架屏暂时不能抽成公共组件全局通用。
首页定稿设定的宽度为 1240px ,对小屏不够友好,咱们增长了一版窄版样式兼容小屏。
宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范须要和设计师统一规范,兼容窄版的开发就会变得很是简单。
只须要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新视图 isWide: evt.detail.isWide }); }); }
为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统作法是 css3 实现,这须要逐帧写动画细节,工做量很是大,咱们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现可以彻底还原AE动画。
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序能够像使用静态图像同样轻松地使用动画。这样实现起来就很是简单了。分如下两步:
lottie-web 文档中的方法很是全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操做。
项目示例代码以下:
npm install lottie-web //安装lottie-web import lottie from 'lottie-web' //引入lottie-web到项目中 //lottie-web经常使用方法 this.anim = lottie.loadAnimation({ //加载动画 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定帧区间 this.anim.setDirection(-1);//动画反向播放 this.anim.play();//播放动画 this.anim.pause()//暂停动画
通过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。
这里有 2 个须要注意的点:
以上就是用 Lottie 实现的动画,看到这里,是否是以为 so easy,可是 Lottie 并非万能的,不能解析全部的动画特性,开发前须要先看下支持列表。并和设计师确认是否都支持。
JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就能够作到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现以下:
// 在距离底部200px时,加载楼层组件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距离底部-200px时,加载入场动画组件,这时由于楼层组件已经加载过了,页面显示是真实组件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div class> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*计算每一个元素的 animation-delay */ } }
产品页的头部动效分两部分,氛围动效 + 波浪动效。
氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,咱们能够拿到须要自定义样式的标签的 id,自定义样式,如图所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定义样式的时候,这里有个坑必定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前尚未找到 ID 值 + 1 的缘由,可是为了让自定义的样式生效,须要给 ID 为 100 和 ID 为 101 的标签都加上样式。
经过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样咱们就能够经过配置参数获得想要的正弦波浪,具体实现以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas宽度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//动画曲线 waves: [//须要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//线条宽度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 "strokeStyle":"rgba(221,221,233,1)",//颜色 "type": function(x, waves) {//自定义波浪类型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋转角度 wavesWidth: '400%',//波浪宽度 });
根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
开发过程当中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 }
最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,作了如下工做:
本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程当中记录和总结,是收获满满的喜悦,也发现了一些能够优化的模块,让下次可以作得更好,在开发过程当中的多些思考和探究,最优化的设计项目。感谢参考原文-http://bjbsair.com/2020-03-27...
通过一个月的时间,在我遇到了不少“这个我不会作啊?”,“这个到底怎么作“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各类问题以及解决方案。
JDR DESIGN 是京东零售设计中台的门户站点,展现京东零售设计服务平台的产品以及应用场景,特色是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。
JDR DESIGN 地址:https://jdrd.jd.com/
这是我入职以来负责的第一个项目,须要花大量时间来熟悉新的开发流程,项目排期很是紧凑,而且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,天天高强度的加班,回想起来虽然很难,可是很是有挑战性,很是有收获。
做为一个 9012 年的 PC 端项目,咱们天然也须要很是先进的技术选型来帮助咱们提高研发生产力,因此一个优秀的前端框架和一个高效的前端工程化工具,天然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,知足了 JDRD 须要兼容IE10的要求。
自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工做,帮助咱们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。
在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,总体架构设计以下:
Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:
整个开发流程总结以下图所示:
既然是总结,开发过程当中的流程固然没有这么完整,漏掉了一部分(已红色标注),致使开发到后面由于前面漏掉的环节,浪费了不少的时间。
数据埋点:交互稿其实有详细介绍哪些地方须要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。
除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。
性能优化的初衷就是加快网站的加载速度,让用户可以更快的看到内容,上面介绍到前端工程化工具 Athena 已经作到合并、压缩了静态资源文件,那还有什么方法可以缩小请求的静态资源体积,加快首屏的加载速度呢,咱们尝试了如下性能优化手段。
楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。
Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你能够把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代码在首次加载时,会先展现一个 loadingBlock,而后动态加载 news_banner 的代码,组件代码加载完毕以后,便会替换掉 loadingBlock。Lazyload 经过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。须要注意的是 lazyload 须要设置高度,才会撑起懒加载的区域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首页为例,有四处组件是不须要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只须要加载首屏的头部、视频、banner 便可。切分以后,首屏 js 体积缩减了 50KB。
整站图片很是多,为了保持清晰度并且所有采用二倍图引入,消耗资源比较大,为了加快加载速度,咱们选择让滚动条滚动到图片的可视区后才加载该图片。
使用 Lazyload 实现,和上述组件懒加载介绍的同样,包裹着须要懒加载的图片,就能够实现图片懒加载。
图片懒加载以外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不一样状态展现图片的内容。
使用 Lazyimg 实现这个功能:
除了上面两点外,还能够从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件能够将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减少 chunks 包的大小。
以往的传统网站通常会在加载中展现一个 loading 态,也能够达到占位的效果,可是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽可能缩小真实模块结构与加载占位之间的视觉差别。
骨架屏的两个用途:
使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提早占位,占位符设置为骨架屏。
设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里若是 loaded === false ,则显示骨架屏。
骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不一样模块引入对应的骨架屏组件,这种方式和 loading 动画同样,耦合度低,可是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每一个页面结构不同,对应的骨架屏也是彻底不一样,骨架屏暂时不能抽成公共组件全局通用。
首页定稿设定的宽度为 1240px ,对小屏不够友好,咱们增长了一版窄版样式兼容小屏。
宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范须要和设计师统一规范,兼容窄版的开发就会变得很是简单。
只须要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新视图 isWide: evt.detail.isWide }); }); }
为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统作法是 css3 实现,这须要逐帧写动画细节,工做量很是大,咱们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现可以彻底还原AE动画。
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序能够像使用静态图像同样轻松地使用动画。这样实现起来就很是简单了。分如下两步:
lottie-web 文档中的方法很是全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操做。
项目示例代码以下:
npm install lottie-web //安装lottie-web import lottie from 'lottie-web' //引入lottie-web到项目中 //lottie-web经常使用方法 this.anim = lottie.loadAnimation({ //加载动画 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定帧区间 this.anim.setDirection(-1);//动画反向播放 this.anim.play();//播放动画 this.anim.pause()//暂停动画
通过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。
这里有 2 个须要注意的点:
以上就是用 Lottie 实现的动画,看到这里,是否是以为 so easy,可是 Lottie 并非万能的,不能解析全部的动画特性,开发前须要先看下支持列表。并和设计师确认是否都支持。
JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就能够作到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现以下:
// 在距离底部200px时,加载楼层组件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距离底部-200px时,加载入场动画组件,这时由于楼层组件已经加载过了,页面显示是真实组件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div class> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*计算每一个元素的 animation-delay */ } }
产品页的头部动效分两部分,氛围动效 + 波浪动效。
氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,咱们能够拿到须要自定义样式的标签的 id,自定义样式,如图所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定义样式的时候,这里有个坑必定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前尚未找到 ID 值 + 1 的缘由,可是为了让自定义的样式生效,须要给 ID 为 100 和 ID 为 101 的标签都加上样式。
经过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样咱们就能够经过配置参数获得想要的正弦波浪,具体实现以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas宽度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//动画曲线 waves: [//须要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//线条宽度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 "strokeStyle":"rgba(221,221,233,1)",//颜色 "type": function(x, waves) {//自定义波浪类型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋转角度 wavesWidth: '400%',//波浪宽度 });
根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
开发过程当中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 }
最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,作了如下工做:
本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程当中记录和总结,是收获满满的喜悦,也发现了一些能够优化的模块,让下次可以作得更好,在开发过程当中的多些思考和探究,最优化的设计项目。感谢参考原文-http://bjbsair.com/2020-03-27...
通过一个月的时间,在我遇到了不少“这个我不会作啊?”,“这个到底怎么作“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各类问题以及解决方案。
JDR DESIGN 是京东零售设计中台的门户站点,展现京东零售设计服务平台的产品以及应用场景,特色是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。
JDR DESIGN 地址:https://jdrd.jd.com/
这是我入职以来负责的第一个项目,须要花大量时间来熟悉新的开发流程,项目排期很是紧凑,而且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,天天高强度的加班,回想起来虽然很难,可是很是有挑战性,很是有收获。
做为一个 9012 年的 PC 端项目,咱们天然也须要很是先进的技术选型来帮助咱们提高研发生产力,因此一个优秀的前端框架和一个高效的前端工程化工具,天然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,知足了 JDRD 须要兼容IE10的要求。
自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工做,帮助咱们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。
在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,总体架构设计以下:
Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:
整个开发流程总结以下图所示:
既然是总结,开发过程当中的流程固然没有这么完整,漏掉了一部分(已红色标注),致使开发到后面由于前面漏掉的环节,浪费了不少的时间。
数据埋点:交互稿其实有详细介绍哪些地方须要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。
除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。
性能优化的初衷就是加快网站的加载速度,让用户可以更快的看到内容,上面介绍到前端工程化工具 Athena 已经作到合并、压缩了静态资源文件,那还有什么方法可以缩小请求的静态资源体积,加快首屏的加载速度呢,咱们尝试了如下性能优化手段。
楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。
Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你能够把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代码在首次加载时,会先展现一个 loadingBlock,而后动态加载 news_banner 的代码,组件代码加载完毕以后,便会替换掉 loadingBlock。Lazyload 经过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。须要注意的是 lazyload 须要设置高度,才会撑起懒加载的区域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首页为例,有四处组件是不须要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只须要加载首屏的头部、视频、banner 便可。切分以后,首屏 js 体积缩减了 50KB。
整站图片很是多,为了保持清晰度并且所有采用二倍图引入,消耗资源比较大,为了加快加载速度,咱们选择让滚动条滚动到图片的可视区后才加载该图片。
使用 Lazyload 实现,和上述组件懒加载介绍的同样,包裹着须要懒加载的图片,就能够实现图片懒加载。
图片懒加载以外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不一样状态展现图片的内容。
使用 Lazyimg 实现这个功能:
除了上面两点外,还能够从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件能够将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减少 chunks 包的大小。
以往的传统网站通常会在加载中展现一个 loading 态,也能够达到占位的效果,可是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽可能缩小真实模块结构与加载占位之间的视觉差别。
骨架屏的两个用途:
使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提早占位,占位符设置为骨架屏。
设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里若是 loaded === false ,则显示骨架屏。
骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不一样模块引入对应的骨架屏组件,这种方式和 loading 动画同样,耦合度低,可是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每一个页面结构不同,对应的骨架屏也是彻底不一样,骨架屏暂时不能抽成公共组件全局通用。
首页定稿设定的宽度为 1240px ,对小屏不够友好,咱们增长了一版窄版样式兼容小屏。
宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范须要和设计师统一规范,兼容窄版的开发就会变得很是简单。
只须要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新视图 isWide: evt.detail.isWide }); }); }
为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统作法是 css3 实现,这须要逐帧写动画细节,工做量很是大,咱们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现可以彻底还原AE动画。
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序能够像使用静态图像同样轻松地使用动画。这样实现起来就很是简单了。分如下两步:
lottie-web 文档中的方法很是全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操做。
项目示例代码以下:
npm install lottie-web //安装lottie-web import lottie from 'lottie-web' //引入lottie-web到项目中 //lottie-web经常使用方法 this.anim = lottie.loadAnimation({ //加载动画 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定帧区间 this.anim.setDirection(-1);//动画反向播放 this.anim.play();//播放动画 this.anim.pause()//暂停动画
通过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。
这里有 2 个须要注意的点:
以上就是用 Lottie 实现的动画,看到这里,是否是以为 so easy,可是 Lottie 并非万能的,不能解析全部的动画特性,开发前须要先看下支持列表。并和设计师确认是否都支持。
JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就能够作到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现以下:
// 在距离底部200px时,加载楼层组件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距离底部-200px时,加载入场动画组件,这时由于楼层组件已经加载过了,页面显示是真实组件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div class> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*计算每一个元素的 animation-delay */ } }
产品页的头部动效分两部分,氛围动效 + 波浪动效。
氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,咱们能够拿到须要自定义样式的标签的 id,自定义样式,如图所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定义样式的时候,这里有个坑必定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前尚未找到 ID 值 + 1 的缘由,可是为了让自定义的样式生效,须要给 ID 为 100 和 ID 为 101 的标签都加上样式。
经过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样咱们就能够经过配置参数获得想要的正弦波浪,具体实现以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas宽度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//动画曲线 waves: [//须要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//线条宽度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 "strokeStyle":"rgba(221,221,233,1)",//颜色 "type": function(x, waves) {//自定义波浪类型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋转角度 wavesWidth: '400%',//波浪宽度 });
根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
开发过程当中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 }
最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,作了如下工做:
本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程当中记录和总结,是收获满满的喜悦,也发现了一些能够优化的模块,让下次可以作得更好,在开发过程当中的多些思考和探究,最优化的设计项目。感谢参考原文-http://bjbsair.com/2020-03-27...
通过一个月的时间,在我遇到了不少“这个我不会作啊?”,“这个到底怎么作“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各类问题以及解决方案。
JDR DESIGN 是京东零售设计中台的门户站点,展现京东零售设计服务平台的产品以及应用场景,特色是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。
JDR DESIGN 地址:https://jdrd.jd.com/
这是我入职以来负责的第一个项目,须要花大量时间来熟悉新的开发流程,项目排期很是紧凑,而且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,天天高强度的加班,回想起来虽然很难,可是很是有挑战性,很是有收获。
做为一个 9012 年的 PC 端项目,咱们天然也须要很是先进的技术选型来帮助咱们提高研发生产力,因此一个优秀的前端框架和一个高效的前端工程化工具,天然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,知足了 JDRD 须要兼容IE10的要求。
自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工做,帮助咱们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。
在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,总体架构设计以下:
Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:
整个开发流程总结以下图所示:
既然是总结,开发过程当中的流程固然没有这么完整,漏掉了一部分(已红色标注),致使开发到后面由于前面漏掉的环节,浪费了不少的时间。
数据埋点:交互稿其实有详细介绍哪些地方须要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。
除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。
性能优化的初衷就是加快网站的加载速度,让用户可以更快的看到内容,上面介绍到前端工程化工具 Athena 已经作到合并、压缩了静态资源文件,那还有什么方法可以缩小请求的静态资源体积,加快首屏的加载速度呢,咱们尝试了如下性能优化手段。
楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。
Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你能够把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代码在首次加载时,会先展现一个 loadingBlock,而后动态加载 news_banner 的代码,组件代码加载完毕以后,便会替换掉 loadingBlock。Lazyload 经过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。须要注意的是 lazyload 须要设置高度,才会撑起懒加载的区域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首页为例,有四处组件是不须要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只须要加载首屏的头部、视频、banner 便可。切分以后,首屏 js 体积缩减了 50KB。
整站图片很是多,为了保持清晰度并且所有采用二倍图引入,消耗资源比较大,为了加快加载速度,咱们选择让滚动条滚动到图片的可视区后才加载该图片。
使用 Lazyload 实现,和上述组件懒加载介绍的同样,包裹着须要懒加载的图片,就能够实现图片懒加载。
图片懒加载以外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不一样状态展现图片的内容。
使用 Lazyimg 实现这个功能:
除了上面两点外,还能够从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件能够将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减少 chunks 包的大小。
以往的传统网站通常会在加载中展现一个 loading 态,也能够达到占位的效果,可是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽可能缩小真实模块结构与加载占位之间的视觉差别。
骨架屏的两个用途:
使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提早占位,占位符设置为骨架屏。
设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里若是 loaded === false ,则显示骨架屏。
骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不一样模块引入对应的骨架屏组件,这种方式和 loading 动画同样,耦合度低,可是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每一个页面结构不同,对应的骨架屏也是彻底不一样,骨架屏暂时不能抽成公共组件全局通用。
首页定稿设定的宽度为 1240px ,对小屏不够友好,咱们增长了一版窄版样式兼容小屏。
宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范须要和设计师统一规范,兼容窄版的开发就会变得很是简单。
只须要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新视图 isWide: evt.detail.isWide }); }); }
为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统作法是 css3 实现,这须要逐帧写动画细节,工做量很是大,咱们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现可以彻底还原AE动画。
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序能够像使用静态图像同样轻松地使用动画。这样实现起来就很是简单了。分如下两步:
lottie-web 文档中的方法很是全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操做。
项目示例代码以下:
npm install lottie-web //安装lottie-web import lottie from 'lottie-web' //引入lottie-web到项目中 //lottie-web经常使用方法 this.anim = lottie.loadAnimation({ //加载动画 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定帧区间 this.anim.setDirection(-1);//动画反向播放 this.anim.play();//播放动画 this.anim.pause()//暂停动画
通过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。
这里有 2 个须要注意的点:
以上就是用 Lottie 实现的动画,看到这里,是否是以为 so easy,可是 Lottie 并非万能的,不能解析全部的动画特性,开发前须要先看下支持列表。并和设计师确认是否都支持。
JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就能够作到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现以下:
// 在距离底部200px时,加载楼层组件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距离底部-200px时,加载入场动画组件,这时由于楼层组件已经加载过了,页面显示是真实组件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div class> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*计算每一个元素的 animation-delay */ } }
产品页的头部动效分两部分,氛围动效 + 波浪动效。
氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,咱们能够拿到须要自定义样式的标签的 id,自定义样式,如图所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定义样式的时候,这里有个坑必定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前尚未找到 ID 值 + 1 的缘由,可是为了让自定义的样式生效,须要给 ID 为 100 和 ID 为 101 的标签都加上样式。
经过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样咱们就能够经过配置参数获得想要的正弦波浪,具体实现以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas宽度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//动画曲线 waves: [//须要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//线条宽度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 "strokeStyle":"rgba(221,221,233,1)",//颜色 "type": function(x, waves) {//自定义波浪类型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋转角度 wavesWidth: '400%',//波浪宽度 });
根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
开发过程当中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 }
最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,作了如下工做:
本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程当中记录和总结,是收获满满的喜悦,也发现了一些能够优化的模块,让下次可以作得更好,在开发过程当中的多些思考和探究,最优化的设计项目。感谢参考原文-http://bjbsair.com/2020-03-27...
通过一个月的时间,在我遇到了不少“这个我不会作啊?”,“这个到底怎么作“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各类问题以及解决方案。
JDR DESIGN 是京东零售设计中台的门户站点,展现京东零售设计服务平台的产品以及应用场景,特色是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。
JDR DESIGN 地址:https://jdrd.jd.com/
这是我入职以来负责的第一个项目,须要花大量时间来熟悉新的开发流程,项目排期很是紧凑,而且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,天天高强度的加班,回想起来虽然很难,可是很是有挑战性,很是有收获。
做为一个 9012 年的 PC 端项目,咱们天然也须要很是先进的技术选型来帮助咱们提高研发生产力,因此一个优秀的前端框架和一个高效的前端工程化工具,天然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,知足了 JDRD 须要兼容IE10的要求。
自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工做,帮助咱们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。
在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,总体架构设计以下:
Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:
整个开发流程总结以下图所示:
既然是总结,开发过程当中的流程固然没有这么完整,漏掉了一部分(已红色标注),致使开发到后面由于前面漏掉的环节,浪费了不少的时间。
数据埋点:交互稿其实有详细介绍哪些地方须要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。
除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。
性能优化的初衷就是加快网站的加载速度,让用户可以更快的看到内容,上面介绍到前端工程化工具 Athena 已经作到合并、压缩了静态资源文件,那还有什么方法可以缩小请求的静态资源体积,加快首屏的加载速度呢,咱们尝试了如下性能优化手段。
楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。
Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你能够把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代码在首次加载时,会先展现一个 loadingBlock,而后动态加载 news_banner 的代码,组件代码加载完毕以后,便会替换掉 loadingBlock。Lazyload 经过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。须要注意的是 lazyload 须要设置高度,才会撑起懒加载的区域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首页为例,有四处组件是不须要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只须要加载首屏的头部、视频、banner 便可。切分以后,首屏 js 体积缩减了 50KB。
整站图片很是多,为了保持清晰度并且所有采用二倍图引入,消耗资源比较大,为了加快加载速度,咱们选择让滚动条滚动到图片的可视区后才加载该图片。
使用 Lazyload 实现,和上述组件懒加载介绍的同样,包裹着须要懒加载的图片,就能够实现图片懒加载。
图片懒加载以外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不一样状态展现图片的内容。
使用 Lazyimg 实现这个功能:
除了上面两点外,还能够从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件能够将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减少 chunks 包的大小。
以往的传统网站通常会在加载中展现一个 loading 态,也能够达到占位的效果,可是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽可能缩小真实模块结构与加载占位之间的视觉差别。
骨架屏的两个用途:
使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提早占位,占位符设置为骨架屏。
设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里若是 loaded === false ,则显示骨架屏。
骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不一样模块引入对应的骨架屏组件,这种方式和 loading 动画同样,耦合度低,可是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每一个页面结构不同,对应的骨架屏也是彻底不一样,骨架屏暂时不能抽成公共组件全局通用。
首页定稿设定的宽度为 1240px ,对小屏不够友好,咱们增长了一版窄版样式兼容小屏。
宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范须要和设计师统一规范,兼容窄版的开发就会变得很是简单。
只须要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新视图 isWide: evt.detail.isWide }); }); }
为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统作法是 css3 实现,这须要逐帧写动画细节,工做量很是大,咱们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现可以彻底还原AE动画。
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序能够像使用静态图像同样轻松地使用动画。这样实现起来就很是简单了。分如下两步:
lottie-web 文档中的方法很是全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操做。
项目示例代码以下:
npm install lottie-web //安装lottie-web import lottie from 'lottie-web' //引入lottie-web到项目中 //lottie-web经常使用方法 this.anim = lottie.loadAnimation({ //加载动画 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定帧区间 this.anim.setDirection(-1);//动画反向播放 this.anim.play();//播放动画 this.anim.pause()//暂停动画
通过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。
这里有 2 个须要注意的点:
以上就是用 Lottie 实现的动画,看到这里,是否是以为 so easy,可是 Lottie 并非万能的,不能解析全部的动画特性,开发前须要先看下支持列表。并和设计师确认是否都支持。
JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就能够作到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现以下:
// 在距离底部200px时,加载楼层组件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距离底部-200px时,加载入场动画组件,这时由于楼层组件已经加载过了,页面显示是真实组件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div class> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*计算每一个元素的 animation-delay */ } }
产品页的头部动效分两部分,氛围动效 + 波浪动效。
氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,咱们能够拿到须要自定义样式的标签的 id,自定义样式,如图所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定义样式的时候,这里有个坑必定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前尚未找到 ID 值 + 1 的缘由,可是为了让自定义的样式生效,须要给 ID 为 100 和 ID 为 101 的标签都加上样式。
经过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样咱们就能够经过配置参数获得想要的正弦波浪,具体实现以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas宽度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//动画曲线 waves: [//须要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//线条宽度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 "strokeStyle":"rgba(221,221,233,1)",//颜色 "type": function(x, waves) {//自定义波浪类型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋转角度 wavesWidth: '400%',//波浪宽度 });
根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
开发过程当中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波长 }
最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,作了如下工做:
本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程当中记录和总结,是收获满满的喜悦,也发现了一些能够优化的模块,让下次可以作得更好,在开发过程当中的多些思考和探究,最优化的设计项目。