前言
因为工作需要,最近在做广电机顶盒app的开发工作。因此对焦点控制进行了组件化。
为了提高开发速度,我们使用了app套壳,内部引用h5页面的开发方式。
摘要
开发环境:机顶盒,安卓系统
前端框架:vue-cli3、vue2、vuex、vue-router
正文
一、环境
1、机顶盒环境下的焦点获取:
遥控器可以通过上下左右,自动获取到有焦点的dom元素。默认可以获得焦点的dom元素,都可以被遥控器选中,同时,没有焦点的元素,可以通过设置tabindex属性来实现焦点的获取。
2、app和安卓环境介绍
因为安全考虑,这里就不提及机顶盒的安卓版本了,只能说它的版本比较低。我们在使用app套壳后,可以支持部分es6语法。这里再提下浏览器的差异,和pc上的chrome浏览器的样式是有差异的,开发完成后,一定要在机顶盒上实机运行下看看效果。
3、为什么需要焦点控制
通过路由跳转页面是不会有焦点控制问题的,因为只有可见域的焦点,才能被遥控器选择到。但是,我们的项目需要弹窗功能,这时,就需要对父页面的焦点进行控制了,因为蒙层是阻止不了焦点的获取的。
二、实现
我的设计思路是通过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('SET_ENABLE', true);
commit('REMOVE_TABINDEX', enablePage);
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;
复制代码
- enable: 用来控制全局焦点是否可用
- enablePage: 用来记录在全局焦点不可用的情况下,可以例外的页面的pageName属性的名称
- 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样式导致的,因为按方向键时,滚动效果优先于焦点移动。