如果想看该实战系列的其他内容,请移步至 Vue.js 实战系列之实现视频类WebApp的项目开发。
项目仓库地址,欢迎 Star
实现效果
功能实现
-
路由配置
设置 推荐 和 关注 的视频播放列表子路由以及解决重复点击导航时,控制台出现报错的问题。
import Vue from 'vue'; import VueRouter from 'vue-router'; import Home from '../views/Home.vue'; Vue.use(VueRouter); const routes = [ { path: '/', redirect: '/index/recommend', }, { path: '/index', redirect: '/index/recommend', }, { path: '/', name: 'Home', component: Home, children: [ { path: '/index', name: 'index', component: () => import(/* webpackChunkName: "index" */ '../views/index/index.vue'), children: [ { path: 'follows', name: 'follows', component: () => import(/* webpackChunkName: "follows" */ '../views/follow/index.vue'), children: [ { path: 'reVideoList', name: 'reVideoList', component: () => import(/* webpackChunkName: "VideoList" */ '../common/components/videoList/VideoList.vue'), }, ], }, { path: 'recommend', name: 'recommend', component: () => import(/* webpackChunkName: "recommend" */ '../views/recommend/index.vue'), children: [ { path: 'reVideoList', name: 'reVideoList', component: () => import(/* webpackChunkName: "VideoList" */ '../common/components/videoList/VideoList.vue'), }, ], }, ], }, { path: '/friends', name: 'friends', component: () => import(/* webpackChunkName: "friends" */ '../views/friends/index.vue'), }, { path: '/news', name: 'news', component: () => import(/* webpackChunkName: "news" */ '../views/news/index.vue'), }, { path: '/mine', name: 'mine', component: () => import(/* webpackChunkName: "mine" */ '../views/mine/index.vue'), }, ], }, { path: '/release', name: 'release', component: () => import(/* webpackChunkName: "release" */ '../views/release/index.vue'), }, ]; const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes, }); // 解决重复点击导航时,控制台出现报错 const VueRouterPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(to) { return VueRouterPush.call(this, to).catch(err => err); }; export default router;
-
实现 VideoList 组件
-
创建 VideoList.vue 组件
采用
vue-awesome-swiper
实现视频列表的全屏滚动。安装:
npm install swiper vue-awesome-swiper --save # or yarn add swiper vue-awesome-swiper
使用:
引入方式分为:Global Registration 和 Local Registration 两种引入方式
在这里我使用的是在组件中引入,全局引入注册只需要在 main.js 中导入即可,具体使用方式可以去官网查看。<template> <div class="video-list"> <swiper ref="mySwiper" :options="swiperOptions"> <swiper-slide> <Videos></Videos> </swiper-slide> <swiper-slide>Slide 2</swiper-slide> <swiper-slide>Slide 3</swiper-slide> <swiper-slide>Slide 4</swiper-slide> <swiper-slide>Slide 5</swiper-slide> <div class="swiper-pagination" slot="pagination"></div> </swiper> </div> </template> <script> import { Swiper, SwiperSlide, directive } from 'vue-awesome-swiper'; import 'swiper/swiper.min.css'; import Videos from './Videos.vue'; export default { name: 'carrousel', components: { Swiper, SwiperSlide, Videos, }, directives: { swiper: directive, }, data() { return { swiperOptions: { // 分页器配置 pagination: { el: '.swiper-pagination', clickable: true, }, // 设定初始化时slide的索引 initialSlide: 0, // Slides的滑动方向,可设置水平(horizontal)或垂直(vertical) direction: 'vertical', // 鼠标覆盖Swiper时指针形状,设置为true时会变成抓手形状 grabCursor: true, // Swiper使用flexbox布局(display: flex),开启这个设定会在Wrapper上添加等于slides相加的宽或高, // 在对flexbox布局的支持不是很好的浏览器中可能需要用到。 setWrapperSize: true, // 自动高度。设置为true时,wrapper和container会随着当前slide的高度而发生变化。 autoHeight: true, // 设置slider容器能够同时显示的slides数量(carousel模式)。 slidesPerView: 1, // 开启鼠标滚轮控制Swiper切换。可设置鼠标选项,或true使用默认值。 mousewheel: true, // 是否开启鼠标控制Swiper切换。设置为true时,能使用鼠标滑轮控制slide滑动。 mousewheelControl: true, // 获取swiper容器的高度。 height: window.innerHeight, // 因为抖音视频的高度是占满整个屏幕的高度 // 抵抗率。边缘抵抗力的大小比例。值越小抵抗越大越难将slide拖离边缘,0时完全无法拖离。 resistanceRatio: 0, // 将observe应用于Swiper的祖先元素。当Swiper的祖先元素变化时,例如window.resize,Swiper更新。 observeParents: true, }, // 标识翻页 page: 1, }; }, computed: { swiper() { return this.$refs.mySwiper.$swiper; }, }, mounted() { console.log('Current Swiper instance object', this.swiper); }, }; </script> <style lang="less" scoped> .video-list { height: 100%; .swiper-container { height: 100%; display: flex; background-color: #000; color: #fff; .swiper-slide { display: flex; justify-content: center; align-items: center; } } } </style>
-
创建 Videos.vue 组件
-
采用
vue-video-player
实现视频播放器。安装:
npm install vue-video-player --save # or yarn add vue-video-player
使用:
引入方式跟上面的插件一样。
-
通过 css 的
transform
属性 改变播放按钮的位置 -
修改按钮样式 通过
/deep/
实现样式穿透。
<template> <div class="videos"> <video-player class="video-player-box" ref="videoPlayer" :options="playerOptions" :playsinline="true" > </video-player> </div> </template> <script> import 'video.js/dist/video-js.css'; import { videoPlayer } from 'vue-video-player'; export default { components: { videoPlayer, }, data() { return { // videojs options playerOptions: { // 默认情况下将会消除任何音频。 muted: true, // 如果true,浏览器准备好时开始回放。 autoplay: false, // 导致视频一结束就重新开始。 loop: true, preload: 'auto', // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器. fluid: true, sources: [{ type: 'video/mp4', // 类型 src: require('@/assets/videos/sucai.mp4'), }], // poster: '/static/images/author.jpg', // 视频宽度,获取客户端宽度 width: document.documentElement.clientWidth, // 允许覆盖Video.js无法播放媒体源时显示的默认信息。 notSupportedMessage: '此视频暂无法播放,请稍后再试', controlBar: false, }, }; }, mounted() { console.log('this is current player instance object', this.player); }, }; </script> <style lang="less" scoped> .videos { height: 100%; width: 100%; padding-bottom: 50px; .video-player-box { height: 100%; width: 100%; display: flex; justify-content: center; align-items: center; /deep/ .vjs-big-play-button { position: absolute; width: 80px; height: 80px; border: none; background-color: transparent; content: none; left: 50%; top: 55%; transform: translate(-50%, -50%); .vjs-icon-placeholder { font-size: 100px; color: rgba(255, 255, 255, 0.7); } } /deep/ .video-js { height: calc(100vh - 50px); } } } </style>
-
-
-
实现视频播放列表的循环渲染
-
VideoList.vue 循环渲染
在原来代码的基础上,修改
swiper-slide
进行v-for
循环,并且添加模拟数据dataList
,将视频传递给子组件<Videos/>
。<template> <div class="video-list"> <swiper ref="mySwiper" :options="swiperOptions"> <swiper-slide v-for="(item , index) in dataList" :key="index"> <Videos :video="item"></Videos> </swiper-slide> <div class="swiper-pagination" slot="pagination"></div> </swiper> </div> </template> <script> import { Swiper, SwiperSlide, directive } from 'vue-awesome-swiper'; import 'swiper/swiper.min.css'; import Videos from './Videos.vue'; export default { name: 'carrousel', components: { Swiper, SwiperSlide, Videos, }, directives: { swiper: directive, }, data() { return { dataList: [ { id: '1', url: 'http://video.jishiyoo.com/3720932b9b474f51a4cf79f245325118/913d4790b8f046bfa1c9a966cd75099f-8ef4af9b34003bd0bc0261cda372521f-ld.mp4', }, { id: '2', url: 'http://video.jishiyoo.com/1eedc49bba7b4eaebe000e3721149807/d5ab221b92c74af8976bd3c1473bfbe2-4518fe288016ee98c8783733da0e2da4-ld.mp4', }, { id: '3', url: 'http://video.jishiyoo.com/549ed372c9d14b029bfb0512ba879055/8e2dc540573d496cb0942273c4a4c78c-15844fe70971f715c01d57c0c6595f45-ld.mp4', }, { id: '4', url: 'http://video.jishiyoo.com/161b9562c780479c95bbdec1a9fbebcc/8d63913b46634b069e13188b03073c09-d25c062412ee3c4a0758b1c48fc8c642-ld.mp4', }, ], // 标识翻页 page: 1, }; }, }; </script>
-
Videos.vue 组件视频播放
Video 是 VideoList 的子组件,通过父子组件传值的方法 在 Videos 组件的
props
属性中接受父组件传过来的值。<template> <div class="videos"> <video-player class="video-player-box" ref="videoPlayer" :options="playerOptions" :playsinline="true" > </video-player> </div> </template> <script> import 'video.js/dist/video-js.css'; import { videoPlayer } from 'vue-video-player'; export default { components: { videoPlayer, }, props: ['video'], // 接收父组件传递过来的数据 data() { return { // videojs options playerOptions: { // ... 其他无关代码省略 sources: [{ type: 'video/mp4', // 类型 src: this.video.url, // 获取VideoList组件传递过来的视频地址 }], }, }; }, }; </script>
以上只是部分代码,请自行整合,完整代码见 Github
-
问题记录
-
Vue 重复点击相同路由,出现
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/index/recommend/reVideoList"
.问题描述:重复点击导航时,控制台出现如上报错信息。
问题原因:产生这个错误信息的原因很简单,就是因为重复点击相同的路由造成的。在 vue router v3.1 版本之后 ,回调形式改成
Promise
api了,返回的是Promise
,如果没有捕获到错误,控制台始终会出现如上图的警告。解决方案:
-
方案一、降低 router 版本
npm i vue-router@3.0 -S
-
在 router/index.js 文件夹下增加下列代码
// src/router/index.js import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const router = new Router({ // routes...配置信息 }) const VueRouterPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(to) { return VueRouterPush.call(this, to).catch(err => err); };
-
捕获异常
this.$router.push(route).catch(err => { console.log('输出报错',err) })
-
跳转时,判断跳转路由和当前路由是否一致,避免重复跳转产生问题
toXXX (item) { if (this.$route.path !== item.url) { this.$router.push({ path: item.url }) } }
-
补齐router第三个参数
// 补齐router.push()的第三个参数 this.$router.push(route, () => {}, (e) => { console.log('输出报错',e) })
-
-
使用 videojs 播放视频提示
(CODE:4 MEDIA_ERR_SRC_NOT_SUPPORTED) No compatible source was found for this video.
问题描述:使用 Videos 播放本地视频时无法播放。
解决方案:
-
将文件放到服务器上,就是别用本地文件的方式打开;
-
播放本地视频文件,使用 require 导入即可;
require('@/assets/videos/index/01.mp4'),
-
用的是 chrome,将网站的 flash 设置成默认允许,然后刷新下,就可以了。
-
如果没有服务器,可以使用下面文件中提供的播放源播放。
-
-
如何更改 Video 组件的播放按钮的样式?
在 vue 组件中,在 style 设置为
scoped
的时候,里面在写样式对子组件是不生效的,如果想让某些样式对所有子组件都生效,可以使用/deep/
深度选择器来实现样式穿透。
总结
通过本章节,可以轻松实现视频列表公共组件的封装。
下面几点是在开发项目时需要注意的点:
- 使用
vue-awesome-swiper
插件实现了全屏滚动; - 使用
vue-video-player
插件实现了移动端视频播放; - 采用
/deep/
实现样式穿透; - 采用
transform
实现位置的移动 - 使用第三方组件时是否采用全局注册的方式。
上一章节: 4. 顶部导航条实现
下一章节: 6. 首页视频详情实现
项目整体介绍:Vue.js 项目实战之实现视频播放类WebApp的项目开发(仿抖音app)
项目仓库地址,欢迎 Star。
有任何问题欢迎评论区留言讨论。