如果想看该实战系列的其他内容,请移步至 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.vue 组件
采用 vue-awesome-swiper
实现视频列表的全屏滚动。
官网: 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
实现视频播放器。
官网: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
,将视频传递给子组件
。
<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 [email protected] -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。
有任何问题欢迎评论区留言讨论。