电视机顶盒WEB开发-前端经验分享

3,297 阅读11分钟

前言

此篇文章不仅是为讲解在电视机顶盒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已经很成熟了,也很流畅,但对于盒子环境而言还是有些慢,还可以做二次优化,所以我基本习惯自己去写,但插件的源码无法放出,我会在下面章节的 “功能实现分析” 中写出大概思路。


常见需求 & 功能实现思路 & 优化思路

根据工作上的业务需求,我会罗列出产品经常想要的功能,并分析相关的思路。

  1. 动画

    这可以说是项目中必定出现的需求,先说下视觉上动画的实现方式

    • 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,这个问题我排查了小半天才发现,需要注意!

  2. 还原页面状态

    这个功能讲起来就是:简单的页面就简单,复杂的页面就很麻烦

    但通常都是通过 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的存储空间,如果你的项目时间充裕建议以此方案进行开发,不要将就。

  3. 不走寻常路的页面还原方案

    很不幸的是:并不是所有浏览器环境都支持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开发者比较少,先看看能否帮助部分人踩坑吧!