开始
87 | 88 |介绍
89 | 90 | TVVM是一个专门为TV WEB APP开发的MVVM模式框架, 它帮助开发者快速开发应用而无需关心焦点控制, 91 | 键盘绑定, 数据绑定等通用逻辑。 92 | 它没有依赖, 体型小巧(20kb) 93 | 94 | 95 |下载
96 |通过npm下载
97 | 你的系统上需要安装有nodejs 98 |
99 | $ npm install -S tvvm
100 |
101 |
102 | 手动下载
103 | 104 | 你也可以通过手动下载的方式, 然后通过<script>标签进行引用, 105 | 下载链接 106 | 107 | 108 |使用
109 |110 |128 | 129 |111 | // es module 112 | import * as TVVM from 'tvvm' 113 | 114 | var tv = new TVVM({ 115 | ...options 116 | }) 117 |
118 |119 | <!-- html --> 120 | <script src="https://unpkg.com/tvvm/dist/tvvm.min.js"></script> 121 | <script> 122 | var tv = new TVVM({ 123 | ...options 124 | }) 125 | </script> 126 |
127 |
特色
130 |焦点自动控制
131 | 132 | 133 | TV应用的特点是使用遥控器控制焦点移动, 而并非鼠标点击/手势触摸事件来触发控件, 从而进行下一步操作。 134 | 这需要我们通过编程的方式控制焦点的移动和跳转。TVVM致力于减少焦点移动的代码逻辑, 通过配置t-index索引, 而非编程的方式解决焦点的移动。 135 |136 | 137 | TVVM把应用分成横轴空间和纵轴空间, 例如 声明 t-index="0-1" 代表应用是第1行 (row=0) 第2列 (col=1)为当前焦点 , 当你点击遥控器的向下移动按钮时 row 增加,col 不变, 此时焦点从 t-index="0-1"移动至 t-index="1-1"的元素上 。 138 | 这些在TVVM内部已经做好了, 开发者只需要在焦点块元素上声明 t-index索引即可。 139 | 140 | 141 | 除此之外, TVVM还提供了丰富的选项, 例如默认焦点, 焦点元素样式, 边界穿越, 按键值配置等。 142 | 143 | 144 |
focus选项
145 |146 |192 |147 | new TVVM({ 148 | ..., 149 | focus: { 150 | defaultFocusIndex: '0-1', // 默认焦点 151 | circle: { 152 | horizontal: true, // 当焦点移动到边界时在水平方向可穿越 153 | vertical: true // 当焦点移动到边界时在垂直方向可穿越 154 | }, 155 | ...options 156 | } 157 | }) 158 |
159 |160 | <style> 161 | *:focus { 162 | outline: none; 163 | border: 2px solid #fff; 164 | } 165 | </style> 166 | <div class="tv"> 167 | <div> 168 | <div> 169 | <div t-index="0-1">0-1</div> 170 | <div t-index="0-2">0-2</div> 171 | <div t-index="0-3">0-3</div> 172 | </div> 173 | </div> 174 | <div> 175 | <div t-index="1-0, 2-0" real-focus="true"> 176 | <span>1-0,</span> <span>2-0</span> 177 | </div> 178 | <div> 179 | <div t-index="1-1, 1-2, 1-3"> 180 | <span>1-1,</span> <span>1-2,</span> <span>1-3</span> 181 | </div> 182 | <div> 183 | <div t-index="2-1">2-1</div> 184 | <div t-index="2-2">2-2</div> 185 | <div t-index="2-3">2-3</div> 186 | </div> 187 | </div> 188 | </div> 189 | </div> 190 |
191 |
显示结果
193 |
按键去抖优化
196 | 197 | 某些物理遥控器在按下按钮时, 有可能高速触发按键事件, 这对应用会产生不良后果, 198 | TVVM在绑定事件的同时在内部会优化操作逻辑, 利用函数去抖控制按键触发频率, 防止因为物理设备差异导致应用逻辑混乱。 199 | 200 | 201 |数据驱动
202 | 203 | TVVM与大多数mvvm思想的前端框架一样, 采用数据驱动的开发模式, 简单来讲,数据驱动使开发者只用关系数据的修改, 而无需手动将数据同步至视图。 204 | 以下是 t-value 指令来实现双向数据绑定的例子, span标签内的内容会随着input输入框的值的改变而改变 205 | 206 |207 |219 |208 | data: function () { 209 | return { 210 | demoInputValue: 'demo' 211 | } 212 | } 213 |
214 |215 | <input t-value="data.demoInputValue" /> 216 | <span>{{data.demoInputValue}}</span> 217 |
218 |
demo
221 | 222 | {{data.demoInputValue}} 223 |API
226 |实例化
227 | new TVVM 返回一个tv实例, 作为该页面的全局单例入口 228 |
229 |
230 | var tv = new TVVM({
231 | el: '#tv',
232 | data () {
233 | return {
234 |
235 | }
236 | },
237 | focus: {
238 |
239 | },
240 | methods: {
241 | testFn: function (a, b) {
242 |
243 | }
244 | }
245 | })
246 |
247 |
248 |
249 | 指令
250 | 251 | TVVM内置指令系统, 包含了一些常用的指令, 用于处理简单的模版逻辑.TVVM内置指令通常以 t- 开头作为标识 252 | 253 |
254 |
255 | <html>
256 | <head></head>
257 | <body>
258 | <div id="tv">
259 | <div t-if="data.dialogVisible" class="dialog">{{data.dialog.title}}</div>
260 | <nav>
261 | <a t-for="item in data.linkList">{{item.label}}</a>
262 | </nav>
263 | <form>
264 | <input t-value="data.dialog.title" />
265 | </form>
266 | <div>
267 | <div " t-index="1-1, 1-2, 1-3"></div>
268 | <div>
269 | <div t-index="2-1"></div>
270 | <div t-index="2-2"></div>
271 | <div t-index="2-3"></div>
272 | </div>
273 | </div>
274 | </div>
275 | </body>
276 | </html>
277 |
278 |
279 | t-index
281 |
285 |
286 | <div class="tv">
287 | <div t-index="0-0">0-0</div> <!-- 第1排第1个元素 -->
288 | <div t-index="0-1">0-1</div>
289 | <div t-index="0-2">0-2</div>
290 | <!-- 第2排第1个元素, 纵向占据2个空间 -->
291 | <div t-index="1-0, 2-0">1-0, 2-0</div>
292 | <div>
293 | <!-- 第2排第2个元素, 横向占据3个空间 -->
294 | <div t-index="1-1, 1-2, 1-3">1-1, 1-2, 1-3</div>
295 | <div>
296 | <div t-index="2-1">2-1</div> <!-- 第3排第2个元素 -->
297 | <div t-index="2-2">2-2</div> <!-- 第3排第3个元素 -->
298 | <div t-index="2-3">2-3</div> <!-- 第3排第4个元素 -->
299 | </div>
300 | </div>
301 | </div>
302 |
303 |
304 |
305 | t-if
306 | 307 | 用于显示/隐藏dom节点, 该指令接收一个在data参数上存在的布尔值 308 | 309 |
310 |
311 | <div t-if="data.dialogVisible"></div>
312 |
313 |
314 | t-for
315 | 316 | 用于循环指定的dom节点, 该指令接收一个表达式,如下item代表数组的每一项, array是data上定义的一个数组 317 | 318 |
319 |
320 | <ul>
321 | <li t-for="item in data.array">{{item.label}}</li>
322 | </ul>
323 |
324 |
325 | data bind
326 | 327 | 数据插值, 双花括号({{}})内接收一个data的属性.用于页面内数据插值 328 | 329 |330 |339 | 340 |331 | data: { name: 'float' } 332 |
333 |334 | <span>{{data.name}}</span> 335 | <!-- 渲染为 --> 336 | <span>float</span> 337 |
338 |
t-class
341 | 342 | 样式表绑定, t-class接收一个data上已经定义的样式名数组或对象 343 | 344 |345 |363 |346 | data: function () { 347 | return { 348 | classList1: ['container', 'container-row'], 349 | classList2: { 350 | 'container': true, 351 | 'container-row': false, 352 | 'container-col': true 353 | } 354 | } 355 | } 356 |
357 |358 | <div t-class="data.classList2"></div> 359 | <!-- 渲染为 --> 360 | <div class="container container-col"></div> 361 |
362 |
t-bind
364 | 365 | 属性绑定,t-bind:name="data.name" 简写形式为 :name="data.name" 366 | 367 |368 |383 | 384 |369 | data: function () { 370 | return { 371 | id: 'billboard', 372 | height: '365', 373 | classname: 'spin' 374 | } 375 | } 376 |
377 |378 | <div :id="data.id" :height="data.height" :class="data.classname"></div> 379 | <!-- 渲染为 --> 380 | <div id="billboard" height="365" class="spin"></div> 381 |
382 |
t-value
385 | 386 | 用于input输入数据与data上数据的绑定, input的value会实时同步至t-model绑定的数据 387 | 388 |389 |401 |390 | data: function () { 391 | return { 392 | demoInputValue: 'demo' 393 | } 394 | } 395 |
396 |397 | <input t-value="data.demoInputValue" /> 398 | <span>{{data.demoInputValue}}</span> 399 |
400 |
demo
403 | 404 | {{data.demoInputValue}} 405 |event bind
408 | 409 | 事件绑定 410 | 411 |412 |427 |413 | methods: { 414 | clickHandler: function () { 415 | // do something 416 | }, 417 | clickHandler2: function (param1, param2) { 418 | 419 | } 420 | } 421 |
422 |423 | <div @click="methods.clickHandler"></div> 424 | <div @click="methods.clickHandler2(data.inputValue)"></div> 425 |
426 |
选项
430 | 431 | new TVVM接收一个选项对象作为唯一参数 432 | 433 |
434 |
435 | var tv = new TVVM({
436 | el,
437 | data,
438 | focus,
439 | methods,
440 | lifeHooks,
441 | })
442 |
443 |
444 | el
446 | 447 | new TVVM() 实例挂载的dom元素, 可以是一个元素查找符或者dom节点对象 448 | 449 |
450 |
451 | new TVVM({
452 | el: '#tv',
453 | })
454 |
455 |
456 |
457 | focus
458 | 459 | focus选项用于设置焦点移动, 键值绑定, 默认焦点等逻辑 460 | 461 |
462 |
463 | new TVVM({
464 | focus: {
465 | defaultFocusIndex: '1-0',
466 | keysMap: {
467 | 'up': {
468 | codes: [38, 103],
469 | handler: function (event, node, index, prevNode) {
470 |
471 | }
472 | },
473 | 'g': {
474 | codes: [71],
475 | handler: function (event, node, index, prevNode) {
476 | console.log('you press g')
477 | }
478 | },
479 | },
480 | keysMapMergeCoverage: false,
481 | circle: {
482 | horizontal: true,
483 | vertical: true,
484 | }
485 | }
486 | })
487 |
488 |
489 |
490 | defaultFocusIndex (可选)
492 | 493 | 进入应用默认聚焦的元素的索引, 该参数为空时, 默认聚焦到页面首个焦点元素上 494 | 495 |
496 |
497 | focus: {
498 | defaultIndex: '0-1'
499 | }
500 |
501 |
502 | activeClass (可选)
503 | 焦点元素的样式名 504 |
505 |
506 | focus: {
507 | activeClass: 'high-light'
508 | }
509 |
510 |
511 |
512 | circle (可选)
513 | 514 | 定义焦点在水平/垂直方向上是否可循环移动, 默认false.515 | - horizontal (可选) 焦点元素在水平方向是否可以循环移动, 默认false
516 | - vertival (可选) 焦点元素在水平方向上是否可以循环移动, 默认false 517 | 518 | 519 |
520 |
521 | focus: {
522 | circle: {
523 | horizontal: true,
524 | vertical: true
525 | }
526 | }
527 |
528 |
529 |
530 | keysMap
531 | 532 | 遥控器键盘键值码映射表, 该参数为空时使用默认键值码映射表533 | - 'alias' 对应键值的别名
534 | - codes 对应键值数组
535 | - handler 对应按键值绑定的事件处理函数 参数分别是event(事件), node(当前焦点dom节点索引), index (当前焦点dom节点的t-index值), prevNode(上一个焦点dom节点索引) 536 | 537 |
538 |
539 | focus: {
540 | keysMap: {
541 | 'up': {
542 | codes: [38, 104],
543 | handler: function (event, node, index, prevNode)
544 | },
545 | 'down': {
546 | codes: [40, 98],
547 | handler
548 | },
549 | 'left': {
550 | codes: [37, 100],
551 | handler
552 | },
553 | 'right': {
554 | codes: [39, 102],
555 | handler
556 | },
557 | 'enter': {
558 | codes: [13, 32],
559 | handler
560 | }
561 | }
562 |
563 |
564 |
565 | keysMapMergeCoverage (可选)
566 | 567 | 键值映射表合并策略true为覆盖, false为合并 568 | 569 |
570 |
571 | focus: {
572 | keysMapMergeCoverage: false,
573 | }
574 |
575 |
576 | data
578 | 579 | tv单例的数据对象, 可以是一个函数或者对象, 当该参数为函数时, 取值为该函数的返回值 580 | 581 |
582 |
583 | new TVVM({
584 | data: function () {
585 | return {
586 | title: 'tvvm demo page',
587 | index: '0'
588 | }
589 | }
590 | })
591 |
592 |
593 | methods
594 | 595 | 该参数是一个对象, 存放所有的方法函数 596 | 597 |
598 |
599 | methods: {
600 | methods1: function () {
601 | console.log('methods1')
602 | }
603 | }
604 |
605 |
606 | hooks
607 | 608 | 生命周期钩子函数集合 609 | 610 | 611 | - beforeCreate 612 | 在实例化之前调用, 此时不可访问data613 | - created 614 | 在实例化后调用,此时data已经设置响应式, 并可访问
615 | - beforeMount 616 | 在实例被挂在到真实dom前调用
617 | - mounted 618 | 在实例被挂在到dom上时调用
619 | - beforeUpdate 620 | 响应式data变动从而导致视图更新前调用
621 | - updated 622 | 响应式data变动从而导致视图更新后调用
623 | - beforeDestory 624 | 在实例被销毁前调用
625 | 626 |
627 |
628 | hooks: {
629 | beforeCreate: function () {
630 | // this 指向tv实例
631 | },
632 | mounted: function () {
633 |
634 | },
635 | ...
636 | }
637 |
638 |
639 |