每年的云音乐年度听歌报告,就像一个靠谱的老朋友,总会在忙碌一年的时光尽头里叩响记忆的大门。
首次访问年报活动为例,好的用户体验主要包括以下几个方面:
图片
简而言之,体验优涉及以下几个方面:
1、页面导航:包括首屏秒开,页面转场等
2、资源管理:包括图片、视频、音频和字体包等
3、页面适配:包括文本适配、动效适配和机型适配等
首屏秒开是指用户从点击链接开始到展示页面内容大约在1s左右完成。整个过程经历如图所示:
容器初始化 -> CDN -> TCP 建连 -> html/js/css 加载解析 -> DOM / CSSOM 解析 -> 渲染布局 -> 绘制
图片
对于前端开发来说,优化难度是从右到左,越到左边就越需要跨团队合作来完成。可以简单的分析总结:
为了有效管控年报中多个视图的高效展示和切换,采用了 SPA[1] 的形式对H5进行组织,同时为页面路由提供了路由表的配置。
路由表简单理解是各个子页面路由对象的集合,这个集合可以是全局数组对象,本地文件,或者服务端下发的配置。集合内的顺序决定了用户看到报告页顺序。这样也可以根据产品的述求,灵活调整集合内的顺序,这样就能动态调整页面顺序了。
各个子页面路由对象的属性具体如下。建议不用传统的 path,因为子页面数量多,且名称很难记忆,可以使用 routerIndex,这是子页面在交互稿中的位置代号,方便开发和调试。其中 ignoreSwipe 是使用在下节手势处理中「子容器接管父容器手势」的场景。
export interface PageProps { model: unknown; position?: number; // 页面埋点使用}export interface PageRouteProps { c: ComponentType<PageProps>; cId: string; // 当前页面唯一id routerIndex: number | string; // 路由索引 ignoreSwipe?: boolean; // 是否忽略滑动,默认false}
年报项目中用户可以通过点击、左右滑动、上下滑动来切换页面。为了防止手势冲突,全局只有一个父容器,各个报告页面是子容器;一些手势频控、页面状态变化等通用逻辑统一在父容器中实现。只在父容器中使用 hammerjs 做手势监听,子容器不再负责页面切换的手势监听,关键代码如下。
hammer = new Hammer(reportRef.current); hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL }); hammer.on('swipeleft', onNextPageWrap); hammer.on('swiperight', onPrePageWrap); hammer.on('swipeup', onNextPage); hammer.on('swipedown', onPrePage);
对于子容器,可能会有以下几种特殊情况:
情况一:如果子容器需要感知用户手势事件,可以监听父容器发出的自定义事件。
// 【翻页】动效:下一页export const ON_PAGE_NEXT = 'ON_PAGE_NEXT';// 【翻页】动效:上一页export const ON_PAGE_PREV = 'ON_PAGE_PREV';
情况二:如果子容器需要完全接管父容器的手势监听事件,例如年度歌手相关的所有页面。首先需要在路由表中将 ignoreSwipe=ture 设置忽略手势,然后在监听父容器发出的通知,进行自定义的事件处理。最后当子容器接管结束后,需要根据需要再次触发父容器的切换事件。关键代码如下:
const onNextPage = useCallback( (nextAction) => { if (currentPage >= len - 1) { nextAction(); // 结束接管,再次触发父容器的切换事件 return; } setCurrentPage(currentPage + 1); }, [currentPage, len]);useEffect(() => { bus.on(ON_PAGE_NEXT, onNextPage); return () => { bus.off(ON_PAGE_NEXT, onNextPage); }; }, [onNextPage, onPrePage]);
情况三:如果子容器存在特定区域需要响应特点手势,例如歌手来信页面左右切换是查看歌手来信。这时候需要子容器调用stopPropagation主动阻止手势向父容器传递。
在路由表和手势处理准备就绪后,最后来看看页面之间转场的实现。年报页面的转场效果使用React官方实现的 react-transition-group 组件。由于篇幅限制,这里不详细介绍react-transition-group 组件的底层原理。结合路由表顺序和当前页面位置,通过 z-index 和 match 来控制子页面的层级和显示隐藏。使用 CSSTransition 实现页面间的进场和退场CSS动画。关键代码如下:
(pages || []).map((item, index) => { // zIndex 和 match是关键代码 const zIndex = (pages.length - index) * 100; const match = index === currentIdx; return ( <div key={item.cId} style={{ zIndex, pointerEvents: match ? 'auto' : 'none', overflow: 'hidden', }}> <CSSTransition in={match} timeout={100} ... appear unmountOnExit> <item.c model={item.model} positinotallow={index} /> </CSSTransition> </div> );})
但是以上的页面转场存在一个问题,即页面之间只能存在一种转场方式。如何自定义页面之间的转场效果?基本思路是各自页面维护自己的转场效果;大部分页面只需将转场信息配置到路由表中;小部分页面可以通过页面上下文获得上一个或者下一个页面的信息,动态决定如何进场或者退场。基于该思路,改造路由表对象,新增一个 TransitionParams 协议,并且提供渐隐渐显的默认转场实现。关键代码如下:
export type TransitionParams = { timeout: number | { appear?: number | undefined; enter?: number | undefined; exit?: number | undefined }; classNames: CSSTransitionClassNames;};export interface PageRouteProps { c: ComponentType<PageProps>; cId: string; // 当前页面唯一id routerIndex: number; // 路由索引 transition: TransitionParams; //默认是渐隐渐显的转场效果 ignoreSwipe?: boolean; // 是否忽略滑动,默认false}
CSSTransition 配合改造后的关键代码如下:
<CSSTransition in={match} timeout={item.transition.timeout} // 关键代码 classNames={item.transition.classNames} appear unmountOnExit> <item.c model={item.model} positinotallow={index} /></CSSTransition>
资源管理主要任务是将网络资源下载到本地,最终将本地资源加载到内存,以便程序可以使用这些资源。优化资源管理可以有效提高年报用户体验。通常开发者会通过压缩资源的大小,并通过内容分发网络(CDN)加快资源的下载速度。但是除了这些还有其他通用的方法呢?
在介绍具体优化手段前,先来看以下关键字,这些是性能优化的通用方法。
总结如下图:
图片
接下来,将从图片、视频、字体包等各个资源,进一步解析如何结合上述关键词进行优化。
关键字:提前 preload、懒加载 lazy、缓存 cache
图片资源占整个年报项目资源中的比例是最高的,大概 70% 左右。所以图片展示速度是否足够快和内容是否完整都会直接影响用户体验。
优先将图片资源使用 tinypng 进行手动压缩,在不失真的情况下,保证图片大小足够小。其次正确选择图片格式能有效减少图片大小。其中图片格式很多,主流的有:
不同图片格式在不同场景上使用。格式没选准确,会导致资源浪的费。如在「年度总览」一页中,海浪
本文链接:http://www.28at.com/showinfo-26-99645-0.html云音乐2023年报前端大揭秘
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com