机顶盒环境下h5焦点控制组件的实现

939 阅读3分钟

前言

因为工作需要,最近在做广电机顶盒app的开发工作。因此对焦点控制进行了组件化。

为了提高开发速度,我们使用了app套壳,内部引用h5页面的开发方式。

摘要

开发环境:机顶盒,安卓系统

前端框架:vue-cli3、vue2、vuex、vue-router

正文

一、环境

1、机顶盒环境下的焦点获取:

遥控器可以通过上下左右,自动获取到有焦点的dom元素。默认可以获得焦点的dom元素,都可以被遥控器选中,同时,没有焦点的元素,可以通过设置tabindex属性来实现焦点的获取。

2、app和安卓环境介绍

因为安全考虑,这里就不提及机顶盒的安卓版本了,只能说它的版本比较低。我们在使用app套壳后,可以支持部分es6语法。这里再提下浏览器的差异,和pc上的chrome浏览器的样式是有差异的,开发完成后,一定要在机顶盒上实机运行下看看效果。

3、为什么需要焦点控制

通过路由跳转页面是不会有焦点控制问题的,因为只有非隐藏元素的焦点(display:none的元素及其子元素都不行),才能被遥控器选择到。但是,我们的项目需要弹窗功能,这时,就需要对父页面的焦点进行控制了,因为蒙层是阻止不了焦点的获取的。

二、实现

我的设计思路是通过vuex的响应式编程功能,实现全局的焦点控制。具体实现方式是将所有需要获取焦点的元素封装成组件,在组建中通过computed属性来监听vuex中全局焦点的变化,从而实现焦点的管理。

1、vuex中的实现

首先新建一个tabindex.js文件,文件中声明焦点控制相关的属性:enable、enablePage、markEnable

const tabindex = {
    namespaced: true,
    state: {
        enable: true, // 全局焦点开关
        enablePage: [], // 可获取焦点页面的pageName
        markEnable: true // 蒙层:弹出蒙层时,不能获取焦点。
    },
    mutations: {
        SET_ENABLE: (state, enable) => {
            state.enable = enable;
            console.log("SET_ENABLE", state.enable);
        },
        SET_MARK_ENABLE: (state, enable) => {
            state.markEnable = enable;
            console.log("SET_MARK_ENABLE", state.markEnable);
        },
        PUSH_TABINDEX: (state, enablePage) => {
            if (!state.enablePage.includes(enablePage)) {
                state.enablePage.push(enablePage);
            }
            console.log("PUSH_TABINDEX", state.enablePage);
        },
        REMOVE_TABINDEX: (state, enablePage) => {
            let index = state.enablePage.indexOf(enablePage); 
            if (index > -1) { 
                state.enablePage.splice(index, 1);
            }
            console.log("REMOVE_TABINDEX", state.enablePage);
        }
    },
    actions: {
        disabled({commit}, enablePage){
            return new Promise(resolve => {
                commit('SET_ENABLE', false);
                commit('PUSH_TABINDEX', enablePage);
                resolve();
            });
        },
        enable({commit}, enablePage){
            return new Promise(resolve => {
                commit('REMOVE_TABINDEX', enablePage);
                if (state.enablePage.length <= 0) {
                    commit('SET_ENABLE', true);
                }
                resolve();
            });
        },
        markDisabled({commit}){
            return new Promise(resolve => {
                commit('SET_MARK_ENABLE', false);
                resolve();
            });
        },
        markEnable({commit}){
            return new Promise(resolve => {
                commit('SET_MARK_ENABLE', true);
                resolve();
            });
        },
    }
};

export default tabindex;
  1. enable: 用来控制全局焦点是否可用
  2. enablePage: 用来记录在全局焦点不可用的情况下,可以例外的页面的pageName属性的名称
  3. markEnable:蒙层出现时,所有焦点都不可用,比如loading时

注:pageName属性是个自定义的props属性,可以在弹窗组件中赋值,也可以通过Math.random()方法随机生成,会在弹窗组建中提及。

2、组件中的实现

只要在组件中监听vuex相关属性的变化,来控制组件的tabindex属性,就能实现焦点的统一管理了

<template>
    <div :class="getClass()" :tabindex="computedTabindex" ref="main" v-on="$listeners" >
        <slot></slot>
    </div>
</template>
<script>
export default {
    name: "box-tabindex-box",
    props: {
        tabindex: {
            type: Number,
            default: 1
        },
        focusFlag: {
            type: Boolean,
            default: false
        },
        useFocusClass: {
            type: Boolean,
            default: true
        }
    },
    inject: {
        pageName: {
            default: ""
        }
    },
    computed: {
        computedTabindex: function(){
            let tabindex = this.$store.state.tabindex;
            if (tabindex.markEnable === false) {
                return -1;
            }
            if (tabindex.enable === false) {
                if (tabindex.enablePage[tabindex.enablePage.length - 1] == (this.pageName)) {
                    return this.tabindex;
                }
                return -1;
            }
            return this.tabindex;
        }
    },
    mounted(){
        let that = this;
        this.$nextTick(function(){
            if (this.focusFlag) {
                that.$refs.main.focus();
            }
        });
    },
    methods: {
        getClass(){
            if (this.useFocusClass) {

                return "u-tab-box";
            } else {

                return "";
            }
        }
    }
}
</script>
<style lang="scss" >
    .u-tab-box{

    }
    .u-tab-box:focus{
        background-color: rgba(0, 0, 0, 0.4);
        box-sizing: border-box;
    }
</style>

vue中通过computed计算属性来实现vuex变化的监听,可能还有其他方法,知道的大佬还请多指教。

input这种输入框也可以通过以上方式来实现焦点控制,这里就不再赘述了。

3、弹窗组件

弹窗组件需要增加一个pageName属性,来标识该弹窗页面。同时,通过provide和组件的inject进行匹配传参,让组件知道自己在哪个页面中。

<template>
    <div class="g-mark" v-show="params.openFlag === true" >
        <div class="g-win menu-background bg-image" :class="winClass" >
            <slot></slot>
        </div>
    </div>
</template>
<script>

export default {
    name: "open-win",
    props: {
        pageName: {
            type: String,
            default: "win: " + Math.random()
        },
        winClass: {
            type: String,
            default: ""
        }
    },
    data(){
        return {
            params: {
                openFlag: false
            }
        }
    },
    provide() {
        return {
            pageParams: this.params,
            pageName: this.pageName
        };
    },
    watch: {
        "params.openFlag"(newVal){
            if (newVal === true) {
                console.log("tabindex/disabled", this.pageName);
                this.$store.dispatch("tabindex/disabled", this.pageName);

            } else {
                console.log("tabindex/enable", this.pageName);
                this.$store.dispatch("tabindex/enable", this.pageName);
            }
        }
    },
    methods: {
        open(){
            this.params.openFlag = true;
        },
        close(){
            this.params.openFlag = false;
            this.$emit("winClose");
        }
    }
}
</script>
<style scoped>
.g-mark {
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.2);
    display: flex;
    align-items:center;
    justify-content:center;
    width: 100%;
    height: 100%;
    z-index: 99999;
}
.g-win{
    display: flex;
    min-width: 800px;
    min-height: 600px;
}
</style>

4、页面实现

页面通过引入组件box-open-win并设置pageName属性即可,当然也可以不设置,组件会自动生成随机名称

      <box-open-win ref="selectUser" pageName="SelectUser" winClass="user-win" >
          <select-user />
      </box-open-win>

备注:

1、如果出现焦点获取后,无法选择其他元素的情况,可能是因为父级元素的overflow: scroll样式导致的,因为按方向键时,滚动效果优先于焦点移动。