前言
此篇文章不仅是为讲解在电视机顶盒WEB环境开发的经验,也包含对于简化后浏览器内核的开发环境与低性能设备踩坑。
因为发现目前针对于机顶盒的前端开发分享非常之少,质量也普遍不高,在此做记录分享,文章中所用技术皆为前端技术,不包含Andoird等其他语言,可放心食用。
项目运行环境
由于市面机顶盒生产厂商很多,其中表现基本都有区别,也有内置机顶盒功能的网络电视,所以在硬件不同的基础上,采用统一的软件服务环境开发是很有必要的。
本人开发项目所项目运行环境是Android部门基于Chrome内核的某个版本做技术支持而打包的环境(其中抛弃了某些特性 比如不支持视频音频Cookie等),但有的盒子已经在市场上使用了几年,性能很低,使用的也是更低版本的内核,导致开发上会有一些阻碍,所以若您的环境也有所差异,技术上有疑惑,可留言告诉我。
框架与插件选择
关于框架
目前市面上开发团队用的比较多的框架无非是 React、Vue、Angular,但在此不推荐用这些东西,而是使用jQuery作为开发的基础支持,插件等效果自己开发实现——为了性能与加载速度。因为这些框架的插件在电脑上运行是没有压力的,但若在机顶盒上,其实可以再优化一些的,与其优化别人的代码不如自己写一个 ~ 如果你webpack学得不错,可以搭建一个以jQuery为核心的打包环境,优化加载方式与速度,那就更好了。
选择它的原因是什么:
1.遥控器每次点击后跳转一次新的页面,都会从新请求其他页面的数据,在页面onload之前,android为了不让用户看到闪白,是以黑屏状态显示页面的,当页面加载完成后触发onload,才会展现页面。如若使用框架三巨头来做,页面加载性能会慢那么一点点,当然,可以通过prefetch做优化,但这不是主要原因,请往下看。
2.简化后的浏览器内核对history对象的支持不友好,如果使用react-router,会导致后退出现bug。
3.公司业务开发速度的考量,jQuery较为方便敏捷,不要喜新厌旧,实用才是最重要的。
关于插件
一行文字放不下怎么办?动画循环滚动之 文字单行跑马灯 (作者对于CSS动画兼容性没有做适配,需要修改JS源码,加入CSS前缀的生成)
这里可分享的暂此一个插件,因为目前对于盒子上能用到的插件推荐不多,我注重性能,喜欢写针对于盒子性能优化后的代码逻辑,在尽力的情况下不浪费一丝丝内存(贫穷使人拮据)
比如必定会用到的滚动动画插件,市面上的例如swpie.js已经很成熟了,也很流畅,但对于盒子环境而言还是有些慢,还可以做二次优化,所以我基本习惯自己去写,但插件的源码无法放出,我会在下面章节的 “功能实现分析” 中写出大概思路。
常见需求 & 功能实现思路 & 优化思路
根据工作上的业务需求,我会罗列出产品经常想要的功能,并分析相关的思路。
-
动画
这可以说是项目中必定出现的需求,先说下视觉上动画的实现方式
- loading:采用多帧图 配合 CSS的animation循环实现,而不是Gif,因为我发现Gif在加载初期的动画效果会因JS脚本执行变得卡顿,而CSS3不会,并且GIF在出现透明动画时的表现非常糟糕,边缘会有杂色,当然可以在导出GIF时选择和背景色对应的杂色来达到欺骗视觉的效果,可若页面背景色是未知的呢? 而CSS3配合PNG的多帧动画很好的避免这个问题↓
- 列表滚动:采用 transition 配合 transform: translateZ(0) 激活GPU动画加速 再配合 will-change 属性做动画预加速,will-change 平时开发用的比较少,感兴趣可以了解下,也许你的环境会不支持,但是写上去吧,指不定未来硬件设备升级,动画会因为你而更流畅(但不要在同一个页面大量使用,也会影响性能)
注:绝不要使用jQuery的animate
接下来是脚本上的列表滚动处理,就相对复杂些了,此处分析横轴列表滚动如:
-
固定宽度滚动:
子元素宽度固定已知,只需要知道当前光标在第几个元素,带入index到固定的算法,即可获取滚动距离,较为简单
-
未知宽度滚动:
元素宽度、个数、排序规则未知,可能有十几种不同的宽度,排序的方式是后台定义,若想从右向左滚动下一屏,滚动时机是什么?怎样的时机才是好的体验?
如何计算?
1.如非必要,请避免动态获取参数,已知参数写入DOM标签或DOM内存,不要去动态获取元素的宽/高/边距/距离父级的位置,这对于盒子而言是很耗性能的事情,这些东西可以放在初始化列表的时候轮询计算。
比如有一个列表宽度未知,可能满足滚动五屏的列表,列表尚未滚动,left值则为0
子元素可能有50列,那么第一列 col-01 宽100px 边距18px 第二列 col-02 宽130 边距 = 第一列边距+第一列宽+第二列边距
实际代码:
<div class="listBox"><!--固定容器宽高 比如宽100%高100% 随窗口大小固定--> <div class="list" data-width="266" data-left="0"><!--需要滚动的列表容器 宽度随内部元素宽度改变--> <a class="col-01" data-width="100" data-left="18">第一列</a><!--left = 自身边距 + 前面元素宽度+边距 没有则为0--> <a class="col-02" data-width="130" data-left="136">第二列</a><!--left = 自身边距+前面元素宽度+边距--> </div> </div>
这样想要计算元素的位置,直接读取DOM属性即可,否则每次动画都要读取元素宽、距离父级的宽、父级滚动距离、父级的宽等一些列动态计算的东西,触发重绘机制,非常耗费性能,带来交互上的卡顿,模拟按右键↓ ↑此处可以看到 滚动至第二屏的时候我专门让左侧留了一些像素,方便按左键时 盒子能检测到左侧有焦点
因为遥控器的上下左右按键,盒子是检测元素周围是否存在A标签进行移动的,所以如果恰巧滚动到右侧的宽度正好导致看不到第二页,那么就触发不到滚动,所以还要考虑一下边界的判断,简单比喻下就是if( 滚动列表容器宽 > 父级固定容器宽 && 焦点元素宽+焦点元素left+下一个元素的边距比如18 > 当前视口的宽){ //存在第二页 && 当前元素焦点看不到下一个元素 则在此处使列表稍微多滚动几像素 露出至少1像素的边缘 使盒子可以检测到右侧有焦点可以移动 }
(实际要复杂一些 多很多判断 比如是否可直接翻页、是否翻页到最后一页、还能翻页但盒子检测不到下个元素)这样算下去明显很复杂,那么有没有简单点的方法呢 ?
将他们加载的时候都存入内存的数组中,监听用户按键并阻止默认行为,计算当前焦点在数组的第几个,用户按右键就向后移动一次,查询页面上对应的DOM,再计算他将要去的位置即可,这样获取焦点的任务就交给了我们自己,而不是机顶盒,可控性上升,未知性降低,但不变的是我们还是要预先存入DOM的data-width,data-left等值,为什么不存入内存中而写入标签?也可以存,比如jQuery获取DOM后的 $(dom).data("data-left",500),但产品这时候又提出了一个功能:我要后退时还原前一个页面的状态~
插入小提示: focus到显示器焦点外时会触发盒子默认的scroll机制,导致box的scrollLeft自动改变(即便你已经overflow:hidden),所以每次focus后需要立刻把父元素、body的scrollLeft设为0,这个问题我排查了小半天才发现,需要注意!
-
还原页面状态
这个功能讲起来就是:简单的页面就简单,复杂的页面就很麻烦
但通常都是通过 storage实现的,那我们就先讲它
简单粗暴的方式
其实产品想要的就是跳走前什么样,回来就什么样而已
如果是一个简单的页面(没有滚动,元素就那么几个)
直接在跳走前点击A标签的时候,以当前页面的url为键值
focusJson对象 写入跳走前点击A标签它的ClassName、Href地址、Index值,返回此页时根据这三个值匹配到后,得知跳走前的元素是哪儿个
const seStorage = window.sessionStorage; const pageUrl = window.location.href.split("?")[0]; const QUOTA_EXCEEDED_ERR_CODE = 22; $(a).click(function(){ const focusJson = { className : $(this).attr("class"), href : $(this).attr("href"), index: $(this).index() } try { seStorage.setItem(pageUrl, focusJson); } catch (e) { //防止存不下报错 理应不会出现 因为我们要在页面按返回,或初次进入页面的时候清除掉当前页Storage 防止溢出 //但还是避免下用户连续进超多页面 不按返回的情况 if (e.code === QUOTA_EXCEEDED_ERR_CODE) { seStorage.clear(); seStorage.setItem(pageUrl, focusJson); } } }) ... $(function(){ //页面初始化时查询是否存有storage 若有则寻找对应className 过滤出正确的元素还原焦点状态 并清除本次storage })
动态数据页面也可以简单粗暴
产品开发项目时间紧,不够你考虑优化的时候采用此方案,时间充裕不建议这样,因为不优雅
那就是直接CopyDOM节点转字符串存入storage,回到此页直接扔到页面里去 一般焦点都会有全局唯一的样式名做焦点效果,直接Focus此样式元素,就完整的还原了跳走前的状态
但需要注意以下几点:
1.页码记录,跳走前把动态数据已经请求的页码也存入focusJson,还原的时候载入这个页码,这样往下翻页数据才正确
2.如果这个动态数据页面存在滚动列表,且滚动了很多屏,如何还原?
如果采用上面在标签中写入data-left,data-width的方式,实际上还原后直接将这些参数作为滚动插件初始化时的初始值即可,绑定到滚动插件就可以正常使用了,只写大概思路,实现具体方式需要自己研究哈,感觉并不复杂3.DOM数据量太大,存入会导致Storage溢出
没错 这就是简单粗暴的缺点,大量的data-left,data-width,DOM标签都存入在storage,数据量大的情况下确实会溢出,需要考虑项目使用场景,数据量的大小
符合编程思维的页面还原
对于前端而言,能在内存中处理的就不要放在DOM上,灵活的操作数据才是王道,比如React的虚拟DOM与Diff算法,我们可以在初始化页面列表的时候就将这些元素的left,width等数据存入对象中,转存入storage,在页面初始化的时候读取storage像Ajax请求JSON一样解析它来还原列表,只记录关键的属性值,而不用像上面简单粗暴的方式把那些根本不需要记录的dom标签、class、id等不属于数据的垃圾字节存起来占地方,可以很大的节省storage的存储空间,如果你的项目时间充裕建议以此方案进行开发,不要将就。
-
不走寻常路的页面还原方案
很不幸的是:并不是所有浏览器环境都支持storage,有的环境支持sessionStorage 有的环境sessionStorage和localStorage都支持,而有的环境他们都不支持 …… 那我们就没办法了吗?
-
input type="text"标签实现记录
使用此标签需要注意的问题:1.不能使用type="hidden" 它记录不到的,返回后数据会消失
2.不能使用css的display:none 也会导致返回数据丢失
那么让它这么占着位置也难看,可以有两种方法解决:
1.positoin:absolute; 哪儿远让它去哪儿
2.visibility:hidden; 占位但隐藏,与display:none不同的是它不会使数据丢失
-
其他需求…… 先写这些吧,还有其他的分析,不过有没有人看还不知道 看情况再写,先跨过此部分
性能优化
由于页面都是A标签元素的跳转,我们可以通过监听focus事件得知本次焦点的是哪儿个元素,给它一个焦点样式,并取消上一个焦点样式 例如
$(document).on("foocus","a",function(){
$(".itemFocus").removeClass("itemFocus");
$(this).addClass("itemFocus");
});
但这样就会有一个问题 如果你的页面是动态加载、翻页,页面有100个A标签,你每次Focus都要查询这些标签是否存在itemFocus
倘若还要计算元素的滚动等,那对于用户而言,操作时明显会有响应慢的问题,那么我们可以简单优化下:
let $focus = null;
$(document).on("foocus","a",function(){
$focus === null? $(".itemFocus").removeClass("itemFocus") : $focus.removeClass("itemFocus");
$focus = $(this).addClass("itemFocus");
});
这样每次都把之前的焦点元素存入了内存,下次就不需要轮询查找.itemFocus,效率得到了显著的提升
最后
先写到这里吧,本来想写的很多,但发现写起来是真的累,深感倒不如学些东西去
盒子WEB开发者比较少,先看看能否帮助部分人踩坑吧!