环境
window10
pnpm 8.15.4
node 8.15.4
vite 5.1.4
soybean admin: 1.0.0
native-ui: 2.38.0
vue-tv-focusable: 2.0.1
小米电视 MIUI TV版本:MiTV OS 2.7.1886(稳定版)
飞视浏览器:https://www.fenxm.com/1220.html
这里必须使用飞视浏览器,其它浏览器都不行,在“测试”步骤会告诉你原因。在小米电视安装飞视浏览器可以去小红书查安装教程:苹果手机小米电视安装第三方 app 教程
描述
其实最好的做法还是安卓开发,前端网页开发远没有安卓开发那么方便的去管理焦点,不过简单支持还是能做到的!
大部分的电视浏览器都支持模拟鼠标,就是在浏览器打开一个网页,会出现一个鼠标,可以通过遥控器去移动这个鼠标,点遥控器的OK键就是鼠标单击键,如下图的蓝色鼠标

但是客户反馈说,无法点击登录按钮,点了没有反应。那这时候要借助 vue 插件去实现获取登录按钮的焦点了,不能用浏览器自带的模拟鼠标
实现
找了一圈,找到了一个 vue-tv-focusable 插件。如果有更好用的,欢迎评论区补充
vue-tv-focusable csdn
 vue-tv-focusable 官方文档
 vue-tv-focusable 案例
 vue-tv-focusable 案例源码:
安装插件
pnpm i vue-tv-focusable --save
引入插件
新建一个插件文件,位置:src\plugins\tvfocusable.ts
import type { App } from 'vue';
import focusable from 'vue-tv-focusable'
export function setupTvFocusable(app: App) {
  
  // 1.注册
  app.use(focusable);
  // 2.初始化配置
  app.config.globalProperties.$tv.init({
    focusClassName: 'tv-focus',
  })
  
}
引入插件,位置:src\main.ts
import 'core-js/stable';
import 'regenerator-runtime/runtime'; // 如果你的代码使用了生成器(Generator),你也需要这个 Polyfill
import { createApp } from 'vue';
import './plugins/assets';
import { setupDayjs, setupIconifyOffline, setupLoading, setupNProgress, setupTvFocusable } from './plugins';
import { setupStore } from './store';
import { setupRouter } from './router';
import { setupI18n } from './locales';
import App from './App.vue';
async function setupApp() {
  setupLoading();
  setupNProgress();
  setupIconifyOffline();
  setupDayjs();
  const app = createApp(App);
  setupStore(app);
  setupTvFocusable(app); // 这里
  await setupRouter(app);
  setupI18n(app);
  app.mount('#app');
}
setupApp();
引入完成之后,使用插件,实现聚焦登录按钮
<NButton
  v-focusable
   class="flex-1"
   block
   @click="toggleLoginModule('pwd-login')"
 >
   {{ $t(loginModuleRecord["pwd-login"]) }}
</NButton>
仅仅使用 v-focusable 指令还不够,还要写聚焦样式,不然登录按钮被选中都不知道。有两种方式,
一个是全局聚焦样式,位置:src\styles\css\global.css
.tv-focus {
  transform: scale(1.01);
  border: 1px solid #FF9933;
  box-shadow: 0 0 20px #FF9933;
}
一个是局部聚焦样式
<style scoped>
.tv-focus {
  /* 电视用遥控器选择或者电脑键盘选择的时候,能给聚焦对象一个放大+灰色阴影的聚焦效果 */
  transform: scale(1.02);
  box-shadow: 0 0 15px rgb(207, 207, 207);
}
</style>
看需求去选择,我这里直接全局设置元素聚焦样式(橙色边框,橙色阴影,放大效果)。
那么如何去测试呢,开发环境不可能一直打开电视去测试,这里我们可以用键盘事件模拟遥控器事件:
| 键盘 | 遥控器 | 
|---|---|
| enter键 | OK键 | 
| 方向键 | 方向键 | 
看看测试效果

 下面是实现输入框聚焦
<script setup lang="ts">
function handleUserNameClick() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.focus();
}
function handleUserNameBlur() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.blur();
}
function handlePasswordClick() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.focus();
}
function handlePasswordBlur() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.blur();
}
</script>
<template>
	...
	<NFormItem path="userName">
      <NInput
        v-focusable
        v-model:value="model.userName"
        :placeholder="$t('page.login.common.userNamePlaceholder')"
        @click="handleUserNameClick"
        @on-blur="handleUserNameBlur"
      />
    </NFormItem>
    <NFormItem path="password">
      <NInput
        v-focusable
        v-model:value="model.password"
        type="password"
        show-password-on="click"
        :placeholder="$t('page.login.common.passwordPlaceholder')"
        @click="handlePasswordClick"
        @on-blur="handlePasswordBlur"
      />
    </NFormItem>
    ...
</template>
除了加入 v-focusable指令,还监听了输入框的 click事件、on-blur事件
来看看测试效果

 输入账号密码登录进去之后,到了首页,还有按钮聚焦(全屏按钮,切换语言按钮,切换主题按钮),这个就不重复代码了,都是按钮聚焦。

下面实现下拉菜单的聚焦,按理说电视app根本不会做这种交互,但是这里还是可以分享一下做法
src\components\common\lang-switch.vue
<script setup lang="ts">
import type { VNode } from 'vue';
import type { DropdownOption, DropdownGroupOption } from "naive-ui";
import { computed, h, getCurrentInstance } from 'vue';
import { $t } from '@/locales';
import FocusableWrapper from "@/components/custom/focusable-wrapper.vue";
defineOptions({
  name: 'LangSwitch'
});
interface Props {
  /** Current language */
  lang: App.I18n.LangType;
  /** Language options */
  langOptions: App.I18n.LangOption[];
  /** Show tooltip */
  showTooltip?: boolean;
}
const { proxy, ctx } = getCurrentInstance();
const props = withDefaults(defineProps<Props>(), {
  showTooltip: true
});
type Emits = {
  (e: 'changeLang', lang: App.I18n.LangType): void;
};
const emit = defineEmits<Emits>();
const tooltipContent = computed(() => {
  if (!props.showTooltip) return '';
  return $t('icon.lang');
});
const renderOption = (props: { node: VNode, option: DropdownOption | DropdownGroupOption }) => {
  return h(FocusableWrapper,
    {
      class: "tv-focus-lang-dropdown-option"
    },
    [
      h(props.node)
    ]
  )
}
function changeLang(lang: App.I18n.LangType) {
  emit('changeLang', lang);
}
function handleUpdateShow(value: boolean) {
  if (value === true) {
    // 下拉菜单开启状态
    const oDropdown = document.querySelector(".tv-focus-lang-dropdown");
    proxy.$tv.limitingEl = oDropdown; // 点击浮层,限制只能在下拉浮层里移动焦点
  } else {
    proxy.$tv.resetLimitingEl(); // 收起浮层,解除限制
  }
}
</script>
<template>
  <NDropdown class="tv-focus-lang-dropdown" :value="lang" :options="langOptions" trigger="click"
    :render-option="renderOption" :on-update:show="handleUpdateShow" @select="changeLang">
    <div>
      <ButtonIcon focusable :tooltip-content="tooltipContent" tooltip-placement="left">
        <SvgIcon icon="heroicons:language" />
      </ButtonIcon>
    </div>
  </NDropdown>
</template>
<style scoped></style>
src\components\custom\focusable-wrapper.vue
<script setup lang="ts">
defineOptions({
  name: "FocusableWrapper",
});
</script>
<template>
  <div v-focusable v-bind="$attrs">
    <slot></slot>
  </div>
</template>
主要通过renderOption 这属性给下拉菜单的菜单项去设置元素焦点, 然后 handleUpdateShow 方法去限制焦点的范围。
在聚焦全屏按钮,并使用全屏按钮的时候遇到了问题:当页面全屏标签页内容时,摁方向键全部元素都无法聚焦。
具体什么原因还分析不出来,全屏后,不论怎么摁键盘的方向键,所有元素都失去了聚焦效果,解决方案如下:
src\layouts\modules\global-tab\index.vue
<script setup lang="ts">
import { nextTick, reactive, ref, watch, getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
watch(
  () => appStore.fullContent,
  async (val) => {
    if (val === true) {
      // 全屏状态
      // fix: 解决全屏标签页内容时按方向键全部元素都无法聚焦的问题
      // 找不到原因,莫名其妙无法聚焦,手动用next聚焦
      await nextTick();
      const oBtn = document.querySelector("#tv-tab-fullscreen-btn");
      proxy.$tv.next(oBtn);
    }
  },
  { immediate: true }
);
</script>
<template>
	...
	<FullScreen id="tv-tab-fullscreen-btn" :full="appStore.fullContent" @click="appStore.toggleFullContent" />
	...
</template>
简单兼容电视TV端遥控器交互,常用的基本就是以上的场景。
### 测试
前面提供了飞视浏览器的下载地址,需要关闭鼠标模拟才能聚焦成功。经过测试发现只有飞视浏览器才有这个关闭模拟鼠标的设置,其它浏览器没有这样的设置

 
 
 设置完了,在地址栏输入访问地址,使用遥控器查看你的元素聚焦效果
最后
做了一大通操作,才能支持兼容电视TV端,而且只能在飞视浏览器访问项目,很局限,有条件还是安卓原生开发
博客分享了vue-tv-focusable 插件,实现按钮聚焦、输入框聚焦、下拉菜单聚焦,解决了当页面全屏标签页内容时,摁方向键全部元素都无法聚焦的问题。有疑问或者遇到什么问题可以评论区补充,感谢观看



















