video标签自己自带的controls控件不能禁止下载
禁止下载:controlslist="nodownload" 但是这个只能在chrome58以上使用
所以只能手写控件

progress-bar:视频进度条总长度

progress-bar__current:当前视频进度条长度

先完成html部分
<div class="shadow hidden"> <!-- 遮罩层-->
<a class="close">x</a> <!--关闭按钮,点击后视频和遮罩层一起关闭-->
<div class="video-container"><!--放视频的容器-->
<video src="" class="player" ></video> <!--video标签-->
<div class="control-btn"><!--视频中间出现的大的播放/暂停控件,鼠标移入视频时出现,离开视频时消失-->
<i class="iconfont v-bofang"></i>
</div>
<div class="controller"> <!--自己写的视频控件-->
<div class="play-button"> <!--暂停播放按钮-->
<i class="iconfont v-play"></i>
</div>
<div class="progress-box"><!--视频进度条父元素(拖拽进度条中用到)-->
<div class="progress-box__shadow"> </div><!--视频进度条遮罩层(拖拽进度条中用到)-->
<div class="progress-bar"> <!--视频进度条容器,也是视频进度条总长度-->
<div class="progress-bar__current"></div><!--视频进度条的当前进度-->
<div class="progress-bar__circle"></div><!--视频进度条上的小圆点-->
</div>
</div>
<div class="duration"> <!--视频时长-->
<div class="current-time">0:00</div> <!--开始时间和当前播放时长-->
/
<div class="total-time"></div> <!--视频总时长,需要获取,用innerHTML写上去-->
</div>
<div class="sound"><!--视频音量-->
<div class="sound-button">
<i class="iconfont v-sound"></i><!--音量图标-->
</div>
<div class="sound-box"><!--和视频一样的结构,音量进度条盒子-->
<div class="sound-box__shadow"></div><!--音量进度条遮罩层-->
<div class="sound-bar"> <!--音量进度条-->
<div class="sound-bar__circle"></div> <!--当前音量进度条-->
<div class="sound-bar__current"></div> <!--音量进度条小圆点-->
</div>
</div>
</div>
<div class="fullscreen"><!--全屏图标-->
<i class="iconfont v-fullscreen"></i>
</div>
</div>
</div>
</div>
js部分
①方法:接受一个url就可以播放视频
<script>
function play(url) {
//找到视频
const player =new VideoPlayer('.shadow .player');
//找到遮罩层
const shadow =document.querySelector('.shadow');
//找到关闭按钮
const close =document.querySelector('.shadow .close');
//找到视频的容器
const videoContainer =document.querySelector('.shadow .video-container');
//打开遮罩层
shadow.classList.remove('hidden');
//播放视频 ------ play方法在class对象里(后文会写到)
player.play(url);
//点击关闭按钮
close.addEventListener('click',(e) => {
//阻止a标签本来的事件
e.stopPropagation();
//关闭视频-----close方法在class对象里
player.Close();
//关闭遮罩层
shadow.classList.add('hidden');
});
//点击遮罩层
shadow.addEventListener('click',() => {
//关闭视频
player.Close();
//关闭遮罩层
shadow.classList.add('hidden');
});
//点击遮罩层的视频容器时,视频不能关闭
videoContainer.addEventListener('click',(e) => {
//阻止冒泡
e.stopPropagation();
});
}
</script>
②写视频播放控件的所有事件
用类对象写
类实际上是个特殊的函数,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成
部分:类表达式和类声明。
定义一个类的一种方法是使用一个类声明。要声明一个类,你可以使用带有class关键字的类名
函数声明和类声明之间的一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError(引用错误)
类声明和类表达式的主体都执行在严格模式下。
constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class创建的对象。一个类只能拥有一个名为 “constructor”的特殊方法。如果类包含多个constructor的方法,则将抛出 一个SyntaxError
一个构造函数可以使用 super关键字来调用一个父类的构造函数。
static 关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。
当一个对象调用静态或原型方法时,如果该对象没有“this”值
更多类相关知识在:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes中
<script>
//图标对象(用unicode引用的方式使用图标)
const icons = {
play: '',
pause: '',
fullscreen: '',
sound: '',
mute: '',
bigPlay: '',
bigPause: '',
};
class VideoPlayer {
//初始化
constructor(playerElement) {
// 传进来的player
this.$el = playerElement;
//拖拽进度条时,初始化一个布尔值禁止鼠标移动元素
this.moving = false;
//video标签元素
this.$audio = document.querySelector(playerElement);
//video标签的父元素:视频容器
this.$videoContainer = document.querySelector('.video-container');
//视频自动播放
this.$audio.autoplay = true;
//视频自动循环
this.$audio.loop = true;
//初始化音量为0.5
this.$audio.volume = 0.5;
//控制条div class='controller'
this.$controller = document.querySelector('.video-container .controller');
//视频开关图标
this.$playButton = this.$controller.querySelector('.play-button');
//视频进度条
this.$progress = this.$controller.querySelector('.progress-bar');
//音量进度条
this.$soundBar = this.$controller.querySelector('.sound-bar');
//音量图标
this.$soundButton = this.$controller.querySelector('.sound-button');
//全屏图标
this.$fullscreenButton = this.$controller.querySelector('.fullscreen');
//视频进度条圆点
this.$pin = this.$progress.querySelector('.progress-bar__circle');
//音量进度条圆点
this.$sound = this.$soundBar.querySelector('.sound-bar__circle');
//视频进度条和遮罩层父元素:盒子
this.$progressBox = this.$controller.querySelector('.progress-box .progress-box__shadow');
//音量进度条和遮罩层父元素:盒子
this.$soundBox = this.$controller.querySelector('.sound-box .sound-box__shadow');
//视频中间的大图标
this.$controlBtn = document.querySelector('.control-btn');
//视频进度条
this.videoBar = this.$progress.querySelector('.progress-bar__current');
//声音进度条长度
this.voiceBarWidth = parseFloat(window.getComputedStyle(this.$soundBar).width);
//声音进度条
this.Voicebar = this.$soundBar.querySelector('.sound-bar__current');
//初始化进度条长度状态变量
this.statusClock = "";
//初始化预保存的音量状态变量
this.previousVol = 0;
//内部构造器调用完成视频各种功能的方法
this.bindEvent();
}
//静态方法:计算视频时长
static formatTime(time) {
let min = Math.floor(time / 60);
let sec = Math.floor(time % 60);
sec = sec > 10 ? "" + sec : "0" + sec;
return "0" + min + ":" + sec;
}
//方法:播放视频并更换图标
playVideoAndChangeIcon(element, iconPause, iconPlay) {
const icon = element.querySelector('.iconfont');
//如果播放按钮上有play样式
if (icon.classList.contains('play')) {
//播放按钮变成停止按钮,播放视频
icon.classList.remove('play');
icon.innerHTML = iconPause;
this.$audio.play();
} else {
//否则按钮变成播放按钮,停止播放视频
icon.classList.add('play');
icon.innerHTML = iconPlay;
this.$audio.pause();
}
}
//方法:声音和视频的mousedown事件
myDown(elem) {
//可以移动元素
this.moving = true;
//打开遮罩层
elem.style.display = 'block';
}
//方法:声音和视频的mouseup事件
myUp(elem) {
//禁止移动元素
this.moving = false;
//关闭遮罩层
elem.style.display = 'none';
}
//方法:播放视频-----外部调用的方法
play(url) {
//因为关闭视频的时候$audio被定义为null,所以播放时要重新定义$audio
this.$audio = document.querySelector(this.$el);
this.$audio.src = url;
}
//方法:关闭视频-----外部调用的方法
Close() {
this.$audio.pause();
//关闭视频的同时清除更新定时器
clearInterval(this.statusClock);
}
//方法:结束播放-----this.bindEvent调用的方法
pause() {
this.$audio.pause();
}
//视频状态的更新----this.bindEvent调用的方法
updateStatus() {
//current-time上写视频播放的当前时间
this.$controller.querySelector(".current-time").innerHTML =VideoPlayer.formatTime(this.$audio.currentTime);
//进度条长度
let percent = this.$audio.currentTime / this.$audio.duration;
this.$controller.querySelector(".progress-bar__current").style.width = percent * 100 + '%';
this.$controller.querySelector(".progress-bar__circle").style.left = percent * 100 + '%';
}
//内部构造器调用的完成视频各种功能的方法
bindEvent() {
const _this = this;
//禁止右键功能
this.$audio.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
//点击播放按钮播放视频
this.$playButton.addEventListener('click', () => {
this.playVideoAndChangeIcon(this.$playButton, icons.pause, icons.play);
});
//点击音量按钮,静音
this.$soundButton.addEventListener('click', () => {
const icon = this.$soundButton.querySelector('.iconfont');
//如果此时的icon为音量按钮
if (icon.classList.contains('voice')) {
icon.classList.remove('voice');
//换图标
icon.innerHTML = icons.sound;
//恢复当前视频的音量为之前保存的静音前的音量状态
this.$audio.volume = this.previousVol;
//回复变量previousVol为初始状态
this.previousVol = 0;
//进度条长度
//如果音量为0
if(this.$audio.volume === 0){
//进度条的长度
this.$sound.style.left = 3 + 'px';
this.Voicebar.style.width = 3 + 'px';
}else {//否则的长度
this.$sound.style.left = parseFloat(window.getComputedStyle(this.$soundBar).width) * this.$audio.volume + 'px';
this.Voicebar.style.width = parseFloat(window.getComputedStyle(this.$soundBar).width) * this.$audio.volume + 'px';
}
} else {//否则
//icon为静音按钮
icon.classList.add('voice');
icon.innerHTML = icons.mute;
//保存当前音量状态
this.previousVol = this.$audio.volume;
//静音
this.$audio.volume = 0;
//进度条长度:因为小圆点也有宽度,所以要计算进去,我设置的小圆点的宽为10px
this.$sound.style.left = 3 + 'px';
this.Voicebar.style.width = 3 + 'px';
}
});
//点击进度条移动视频进度条和圆点
this.$progress.addEventListener('click', event => {
const barVideoWidth = parseFloat(window.getComputedStyle(this.$progress).width);
let percent = event.offsetX / barVideoWidth;
this.$audio.currentTime = this.$audio.duration * percent;
this.$pin.style.left = event.offsetX + 'px';
this.videoBar.style.width = event.offsetX + 'px';
this.auto = true;
});
//拖拽圆点移动视频进度条和圆点
this.$pin.addEventListener('mousedown', () => {
//允许鼠标移动圆点并打开遮罩层
this.myDown(this.$progressBox);
});
//注意:为了避免点击到进度条,所以鼠标移动事件绑定到遮罩层上,圆点在遮罩层上移动
//这样滑动圆点的时候就不会出现由于点到进度条而移动失败
this.$progressBox.addEventListener('mousemove', event => {
//阻止冒泡
event.stopPropagation();
if (this.moving) {
const barVideoWidth = parseFloat(window.getComputedStyle(this.$progress).width);
let percent = event.offsetX / barVideoWidth;
this.$audio.currentTime = this.$audio.duration * percent;
this.$pin.style.left = event.offsetX + 'px';
this.videoBar.style.width = event.offsetX + 'px';
}
});
document.addEventListener('mouseup', () => {
//不允许鼠标移动圆点并关闭遮罩层
this.myUp(this.$progressBox);
});
//点击移动音量进度条和圆点
this.$soundBar.addEventListener('click', event => {
//音量要大于0,不加这个出现过音量小于0的bug
let x = Math.max(0, event.offsetX);
this.$audio.volume = x / this.voiceBarWidth;
this.$sound.style.left = event.offsetX + 'px';
this.Voicebar.style.width = event.offsetX + 'px';
const icon = this.$soundButton.querySelector('.iconfont');
if (event.offsetX <= 3) {
this.$audio.volume = 0;
icon.innerHTML = icons.mute;
this.$sound.style.left = 3 + 'px';
this.Voicebar.style.width = 3 + 'px';
} else {
icon.innerHTML = icons.sound;
}
});
//拖拽移动音量进度条和圆点
this.$sound.addEventListener('mousedown', () => {
this.myDown(this.$soundBox);
});
this.$soundBox.addEventListener('mousemove', event => {
event.stopPropagation();
if (this.moving) {
let x = Math.max(0, event.offsetX);
this.$audio.volume = x / this.voiceBarWidth;
this.$sound.style.left = event.offsetX + 'px';
this.Voicebar.style.width = event.offsetX + 'px';
const icon = this.$soundButton.querySelector('.iconfont');
if (event.offsetX <= 4) {
this.$audio.volume = 0;
icon.innerHTML = icons.mute;
this.$sound.style.left = 4 + 'px';
this.Voicebar.style.width = 4 + 'px';
} else {
icon.innerHTML = icons.sound;
}
}
});
document.addEventListener('mouseup', () => {
this.myUp(this.$soundBox);
});
//播放时
this.$audio.addEventListener('play', () => {
//先清除一次计时器
clearInterval(this.statusClock);
//0.5秒更新一次进度条长度
this.statusClock = setInterval(function () {
_this.updateStatus();
}, 500);
//视频总共有多长的时间
this.$controller.querySelector(".total-time").innerHTML = VideoPlayer.formatTime(this.$audio.duration);
});
//暂停时清除计时器
this.$audio.addEventListener("pause", function () {
clearInterval(this.statusClock);
});
//全屏和取消全屏
//只能网页全屏不能电脑屏幕全屏,因为电脑屏幕全屏后不知为何视频无法点击全屏
this.$fullscreenButton.addEventListener('click', () => {
if (!this.$videoContainer.classList.contains("open")) {
this.$videoContainer.classList.add('open');
} else {
this.$videoContainer.classList.remove("open");
}
});
//大图标控制播放和关闭
//大图标的出现和消失
this.$videoContainer.addEventListener('click', () => {
//点击屏幕,播放图标出现并视频暂停
this.playVideoAndChangeIcon(this.$controlBtn, icons.bigPause, icons.bigPlay);
this.$controlBtn.style.display = 'block';
});
//点到控制条时
this.$controller.addEventListener('click',(e) => {
//阻止冒泡
e.stopPropagation();
});
//鼠标移入视频屏幕
this.$videoContainer.addEventListener('mouseenter',()=>{
//如果视频处于暂停状态
if(this.$audio.paused){
//播放图标出现
this.$controlBtn.style.display = 'block';
}
});
//鼠标移出屏幕
this.$videoContainer.addEventListener('mouseleave', () => {
//播放图标消失
this.$controlBtn.style.display = 'none';
});
}
}
</script>
最后是css样式:
/*引入并使用图标前记得去网站下载图标代码,保存在自己项目中*/
@font-face {
font-family: 'iconfont'; /* project id 1324873 */
src: url('//at.alicdn.com/t/font_1324873_mett0wusfe.eot');
src: url('//at.alicdn.com/t/font_1324873_mett0wusfe.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_1324873_mett0wusfe.woff2') format('woff2'),
url('//at.alicdn.com/t/font_1324873_mett0wusfe.woff') format('woff'),
url('//at.alicdn.com/t/font_1324873_mett0wusfe.ttf') format('truetype'),
url('//at.alicdn.com/t/font_1324873_mett0wusfe.svg#') format('svg');
}
.iconfont{
font-family:"iconfont" !important;
font-size:16px;font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
}
* {
box-sizing: border-box;
}
.iconfont {
color: white;
}
@keyframes show {
from {
bottom: -30px;
}
to {
bottom: 0;
}
}
.shadow {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, .6);
}
.shadow.hidden {
display: none;
}
.shadow .close{
display:block;
width: 32px; height:32px;
border-radius:50%;
position:absolute;
right:20px; top:20px;
text-align:center;
line-height: 26px;
font-size: 24px;
color:#aaaaaa;
text-decoration: none;
box-shadow:0 0 7px 1px #aaaaaa;
opacity: 0.5;
transition:all .5s linear;
cursor: pointer;
}
.shadow .close:hover{
box-shadow:0 0 7px 1px #cccccc;
background: #222222;
opacity: 1;
}
.shadow .video-container {
width: 900px;
height: 506px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
background: black;
}
.shadow .video-container.open {
position: absolute;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
right: 0;
top: 0;
overflow: hidden;
transform: none;
}
.shadow .video-container .player {
width: 100%;
height: 100%;
}
.shadow .video-container .controller {
display: none;
position: absolute;
bottom: 0;
height: 40px;
width: 100%;
background-image: linear-gradient(to bottom,rgba(0, 0, 0, .1), rgba(0, 0, 0, 1));
z-index:217483647;
}
.shadow .video-container:hover .controller {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 18px;
animation: show 1s;
}
.video-container .play-button {
padding: 1px;
width: 30px;
}
.controller .progress-bar {
position: relative;
width: 100%;
height: 4px;
border-radius: 8px;
background: rgba(0, 0, 0, .8);
cursor: pointer;
}
.controller .progress-box,.controller .sound-box{
position: relative;
width: 80%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.controller .progress-box .progress-box__shadow,.controller .sound-box .sound-box__shadow{
display: none;
position: absolute;
content: "";
left: 0;
right: 0;
top: 0;
bottom: 0;
background: transparent;
cursor: pointer;
user-select: none;
z-index: 10;
}
.controller .progress-bar .progress-bar__circle {
position: absolute;
top: -5px;
left: 50%;
transform: translate(-50%, 0);
height: 14px;
width: 14px;
border-radius: 50%;
background: #cccccc;
}
.controller .progress-bar .progress-bar__current {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 50%;
border-radius: 8px;
background: #c0c0c0;
z-index:1;
}
.controller .duration {
display: flex;
justify-content: space-around;
align-items: center;
width: 80px;
margin: 0 10px;
font-size: 14px;
color: white;
}
.controller .sound {
width: 100px;
height:100%;
display: flex;
justify-content: center;
align-items: center;
margin: 0 10px;
}
.controller .sound .sound-button {
width: 20px;
margin: 0 10px;
}
.controller .sound .sound-bar {
position: relative;
width: 80px;
height: 4px;
border-radius: 8px;
background: rgba(0, 0, 0, .8);
cursor: pointer;
}
.controller .sound .sound-bar__circle {
position: absolute;
top: -3px;
left: 50%;
transform: translate(-50%, 0);
height: 10px;
width: 10px;
border-radius: 50%;
background: #cccccc;
}
.controller .sound .sound-bar__current {
position: absolute;
top: 0;
bottom: -1px;
left: 0;
width: 50%;
border-radius: 8px;
background: #c0c0c0;
}
.fullscreen{
margin-left: 14px;
}
.control-btn{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
display: none;
opacity: 0.6;
}
.control-btn:hover{
opacity: 1;
}
.control-btn .v-bofang{
font-size:78px;
text-align: center;
color:#ffffff;
}
.control-btn .v-bofang:hover{
font-size:78px;
text-align: center;
}
video::-webkit-media-controls{
display:none !important;
}
我的github示例:[demo](https://evaspec.github.io/video/index.html](https://evaspec.github.io/video/index.html)
我的完整代码:video-demo