实现小米官网视频组件用typescript封装模块

了解项目需求要点
webpack的安装和使用
(1)创建webpack与loader使用
(2)webpack的插件使用
(3)webpack-dev-server的使用
npm i -g webpack //全局安装 (最新版本)
npm i -g webpack@<version>//安装指定版本
全局安装 npm i webpack webpack-cli -g
局部安装 npm i webpack webpack-cli -d
dist文件是生成的封装好的文件夹,如果再dist文件下修改文件,再执行npm run build
则会生成一个未修改前的文件,也就是说不影响源封装好的文件
如果要清除多余的文件,则 npm i -d clean-webpack-plugin,在webpack.config.js中添加const { CleanWebpackPlugin } = require('clean-webpack-plugin');
开启服务器实时监听 安装:npm i -d webpack-dev-server
然后在webpack.config.js下添加deverver字段 :根路径和是否自动打开的方式
打包模式:"build": "webpack --config webpack.config.js",
开发模式:"start": "webpack-dev-server"
支持字体图标的使用
1.打开https://www.iconfont.cn/ 收集下载并加到项目下的iconfont文件夹
2.需要下载file loader 执行:npm i -d file-loader
支持Typescript的使用:加载引入ts模块
(1) npm i -d ts-loader typescript //安装
(2) 到webpack.config.js的moduel下添加:
{
test: /\.ts$/,
use: ['ts-loader'],
exclude: /node_modules/
}
弹层播放器的需求分析:弹层组件配置参数和播放器配置参数
(1)视频列表实现其结构html
(2)视频列表页实现其样式
在main.ts和main.css中实现
组件框架搭建(popup.ts/popup.css)
- 1.怎么去写组件最外层的方法?
- 通过创建对象的方式来提供一个方法接口,或者通过提供一个函数的方式来提供一个方法接口(这里用的函数方法,相当于又做了一层封装)
- 2.创建一个function()以及提供一个对外的接口export default
- 3.具体创建的功能需要通过类Class的方式来实现
- 4.在函数中通过return new 类()的方式来返回对象
- 5.在外面就可以直接用声明的popup的方法,在main.ts全局中引入import然后再调用popup({});传入的是配置对象
- 6.作为组件开发人员,希望业务逻辑按照相关参数来写,怎么约束使用人员能够按照组件配置参数来编写呢?按照接口来约束 interface
- 7.接口声明名后加?为可选参数 ,该项目中的配置参数:宽\高\标题\位置\遮罩层\以及是否要根据弹层容器进行一个自定义编辑,可以定义一个函数,空返回值
- 8.在function中定义一个参数接收传进来的一个接口Ipopup
- 9.使用的话,在main.ts调用的地方要按照接口来编辑。指定几个接口就要写对应多少个参数,有了?可选
- 10.function中接受的option参数要传到类中使用 用constructor(){}构建,用形参settting来接受参数,可匹配接口类型
- 11.需要在类当中让形参setting变成当中对象下的属性。在constructor之前先定义为一个空的对象setting={}
或者在接受参数的setting前加入privite的声明,就可以用 this.setting - this.setting = Object.assign()可以让对象进行拷贝的,在Object.assign({},this.setting),在{}中设置默认值的设值配置
- 13.要做组件,要考虑到做多个组件怎么办?如何做规范化?同样的,再定义一个组件component的接口,这里定义了函数初始化,创建模板,事件操作的函数
- 14.怎么用类来约束类,用implements语法来调用接口,在类下面的方法必须都要加上组件配置的函数的方法才不会报错
- 15.创建的模板,应该有一个父容器,在接口下再加一个tempContainer,可以选HTMLElement作为它的类型,注意:接口中做了定义,在类方法中也要记得做声明,否则报错
- 16.document.createElement()建一个容器,这里用div,用innerHTML进行内容添加
- 17.添加到body中 document.body.appendChild
总结:定义两个接口,一个使用组件配置的接口,主要是用来规范和约束业务开发人员,一个组件接口用来规范和约束组件开发人员
popup.css样式
*1.import引入css样式(三种方式:(1)import './popup.css';//全局css操作
(2)let styles = require('./popup.css');//node模块化的写法引入 webpack的方式 与ts无关
(3)import styles1 from './popup.css';//要写对应的声明(翻译)文件,因为ts不识别css,总的来说在这里算是比较麻烦),用ts模块化的方式
*2.组件的优势:封装性好,所有html/js/ts/css都是封闭的,业务开法人员只需要怎么去用,不要考虑内部是怎么实现的
*3.修改配置文件:区分全局和局部:(1)全局(exclude排除组件部分外的应用),(2)局部(组件,use{}用配置的方法来写loader,做使用范围的允许)
*4.模块化css思想,可加语义化,根据自己的需求模式来自动生成class类的名称
创建弹层布局结构及样式
*1.在template(){}中添加样式
{
this.tempContainer = document.createElement('div');
//html布局
this.tempContainer.innerHTML = <div>...</div>
;
document.body.appendChild(this.tempContainer);
}
*2.dom操作:弹层左/右/中间/的位置
创建遮罩层结构与样式
1.在class下添加createMask()方法,并在init()初始化方式中调用
2.在createMask()方法中使用this.mask做成对象的属性,因为可能在别的方法用到这个方法,
所以要在class下做一个全局的声明
3.如果在main.ts文件下将mask声明为false,则无遮罩效果(默认为true)
4.实现关闭点击事件,先到css中的<i></i>标签对应的添加cursor:pointer手型
5.在初始化init()中调用handle()方法中使用,this.handle()
6.创建contentCallback()方法,并且在初始化init()中调用
7.去获取popup下的容器元素:let popupContent = this.tempContainer.querySelector(div.${styles['default']['popup-content']}
);
8.将这个元素当参数传进去:this.settings.content(popupContent);注意要在接口函数的content中指定参数,才不会报错
创建视频组件架构搭建(vedio.ts/vedio.css)(同弹层组件的方式,以下省略说明)
添加播放器布局结构
添加播放器样式
播放与暂停的实现
当前时间与总时间
播放器全屏功能
播放进度条的实现
拖拽播放进度条与音量进度条
配置自动播放
打包项目案例:
1.package.jsonw文件的buid是开发环境,start是生产环境,都在调用同一个webpage.config.jsonw文件的buid是开发环境,start是生产环境,都在调用同一个webpage
2,正常来说应该是分开的,怎么实现呢?
3.复制webpack.config.js文件给复制一份,文件名要重命名区别为开发环境和生产环境两个项目
4,webpack.config.pro.js 和 webpack.config.dev.js
5.修改build和start的开发环境
"build": "webpack --config webpack.config.pro.js",
"start": "webpack-dev-server --config webpack.config.dev.js"
6.npm start 开发环境的运行,确保没影响
- 主要是要修改生产环境,如:mode为开发环境 mode:"production"
8.npm run build 生产环境 会生成一个dist压缩文件
9.进一步优化引入的iconwen文件,修改webpack.config.pro.js文件的use{} ,iconfont写成配置的写法,
10.css也可以单独提取为一个文件,要用要一个插件,
11.执行npm i -d mini-css-extract-plugin安装
12.下载好之后引入 const MinCssExtractPlugin = require('mini-css-extract-plugin')
13.在plugin模块下调用 new MinCssExtractPlugin()
14.将style-loader改为MinCssExtractPlugin.loader
15.最终生成三个文件:index.html,main.js,main.css以及iconfont文件夹和node_module文件夹
16.可将该文件发送给后端或者上传到云服务即可调用
总结:
1,利用Webpack工具搭建项目环境,让项目支持TS
2,需求分析,弹层组件与播放组件需要哪些配置参数
3,如何设计组件的相关方法以及如何实现对CSS进行模块化
4,如何利用<video>相关的API完成项目开发,可参考02-videoSrc.markdown
5,静态类型检查,可以规范编码的方式,让项目健壮成长
6,相比较JavaScript,虽然开发阶段更耗时,但再也不担心多变的产品需求了
源码
main.ts
//webpack测试
//引入
// import a from './a.js';
// import './a.css';
// //如果能打印出a则说明两个文件已经合并到一块了
// console.log(a);
// // ts测试
// import a from './a';
// // let a: string = 'hello';
// console.log(a);
//开始项目
import './main.css'
import popup from './components/popup/popup';
import video from './components/video/video';
//类型注解,ts会自动推断类型
//listItem获取list li数据
let listItem = document.querySelectorAll('#list li');
for(let i=0;i<listItem.length;i++){
listItem[i].addEventListener('click',function(){
let url=this.dataset.url;
let title=this.dataset.title;
popup({
width:'880px',
height:'556px',
title,
pos:'center',
// mask:true,
content(elem){
video({
url,
elem,
// autoplay:true
});
}
});
});
}
main.css
@font-face {
font-family: "iconfont"; /* Project id 3233007 */
src: url('./iconfont/iconfont.eot?t=1646891516730'); /* IE9 */
src: url('./iconfont/iconfont.eot?t=1646891516730#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAARoAAsAAAAACZAAAAQcAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDQAqFcIUPATYCJAMYCw4ABCAFhGcHYRtcCMgekiSEwEEeABRQEnAcD1/78Tt3d99+16TaxaLiVSUxHaJYInVSZ4jeSXakU092CA00KSUIIcvSvwwovwMoF3A1FFYgnoJTiackw1JgxoBr1MVso//nUjmLsjE2v51iBOYeeBhbGKAAzdlltQqV++oV9C6wcwjUqhOfEWi3oZDkqLK+HWIeM8QGOkGC8aCujsdGnoEw3aTM0EOrtkqvFvEBgkkvJcsA3uc/H78gNWEkTRZ86PxuBQdPVrLWJqXifxHkzyAerg+3JzTIWLkwSzybdz0lLVMrpNrHPh7kAGjXKklfs1Z6VvetTf4/LnZCdmua9lDlEP/wkqIhqkDViYRjQ+PRiWOEzNcehODbPoHj9n0CEQrfJ00a1NUDCinzG8QmEVbcoTx+w6QmOC6Ni5NpVOpxzAc8/kMKDidMr6hEHNN3WM2jpQHPEIhnD5+nqCs+leFRdUYWnIuIeHVmMi4Qz0ZGXvbOWdRdAs+Lc2Ou/logZRxStFOGMzCjmSwNVz9usSg7Nja8Pj85PjbpCVDCByHPvQcd6afaQXO798bxhHsIBgtuKjs6/adUxbNTKZ53EqfVbOCk8O0nVF3eU/7j6vuvKc+rXP+xwGnlw5cgvugYwb7GdRpfdqhL80TtkaP+AJYty3AmK/h9EfJZjG+Cov2KXqdbF31zcemALl53YGnxZrvil6wpT+TjxKVF8uISb/Im7y6eLfXLpsMVQvAtMaMUogZozUPhD8I7Y822fNIW6bbeEfjz+uPthfy4NNkB2YN48qfF5S9a3+BHgOsKT9sQv/7B+vgNaeFdSPLq3RZZnGzLuyvvufiLCVzeltad27c0NiC4SKHryUMZ2cmsfAnJLtKoLqfjItPbcaq+kKMuAnepPLE3qZ8zDz6CKYD/T/NjaRHzU8cuKSBLzOelIutl9ki3Kz62NRv+Yn/jtTsXDcXmf22NLOBLU+kUBf+LBbaJ/iJqYtEfSt0vRC1umFrGhsdtAtX1RkTij9P3H/L1+3QozYC4ktBqoIakxQRkraYwhbAEjQ5bULXahnbLKm53GOAgFaUNc3YDhF5nIen2CbJec5hCeA2NYatQ9foL7a5G9HMd5sKWCYghiKP147SRtdtwpCX5oq2Id1sYYSjeiQSXEaN0SmJyN1uFbEjY44RrhE8lBNNYsFvpSvVjyGKx0w7BbkIsSTQQ4shNSsJjX5LI2q1QRoAwCIRD0xtHM2LZ2eAUnbR/XyuE52bBEISYTofAxUJanJYiUbIAU6WyiYZeyGqXEbxUBIF5Fyaws6JVug7EEqbsaI7xTSYIi0hk2JBwyJVEaFiqSjy9xvoWz0G74KU1UuQo0USNFvWem7HpjXHObInDaOMn3EaOMTRWhOjtmLHxoRPMy/Lb+LDxxGAxdowA') format('woff2'),
url('./iconfont/iconfont.woff?t=1646891516730') format('woff'),
url('./iconfont/iconfont.ttf?t=1646891516730') format('truetype'),
url('./iconfont/iconfont.svg?t=1646891516730#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-guanbi:before {
content: "\e659";
}
.icon-quanpingzuidahua:before {
content: "\e786";
}
.icon-bofang:before {
content: "\e637";
}
.icon-zanting:before {
content: "\e87a";
}
.icon-yinliang:before {
content: "\e87b";
}
*{margin: 0;padding: 0;}
ul{list-style: none;}
img{display: block;}
body{background: #f5f5f5; height: 2000px;}
/* 清除浮动 */
.clearfix::after{content: "";display: block;clear:both;}
/* 选择器 */
#list{width: 1226px; margin: 20px auto;}
#list .list-warp{width: 1240px;}
#list .list-warp li{float: left;width: 296px;height: auto;margin-right: 14px;background: white;cursor: pointer;}
#list .list-warp li:hover i{background: #ff6700;}
#list .list-warp div{position: relative;}
#list .list-warp img{width: 100%;height: 100%;}
#list .list-warp i{position: absolute;bottom: 10px;left: 19px;border: 2px white solid;
color: white; border-radius: 10px; width: 32px; height: 20px; text-align: center;line-height: 20px;
font-size: 12px;}
#list .list-warp h3{font-size: 12px;text-align: center;padding: 30px 0;}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ts封装播放器组件</title>
</head>
<body>
<div id="list">
<ul class="list-warp">
<li data-url="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/812358b69886e576c66a01f1f00affe9.mp4"
data-title="2021年春季新品发布会第一场">
<div>
<img src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/e74c4ff741bcdfc5b28a48a43e4edc6d.jpg?thumb=1&w=296&h=180&f=webp&q=90"
alt="">
<i class="iconfont icon-bofang"></i>
</div>
<h3>2021年春季新品发布会第一场</h3>
</li>
</ul>
</div>
<div id="list">
<ul class="list-warp">
<li data-url="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/812358b69886e576c66a01f1f00affe9.mp4"
data-title="Redmi 10X系列发布会">
<div>
<img src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/101b19aca4bb489bcef0f503e44ec866.jpg?thumb=1&w=296&h=180&f=webp&q=90"
alt="">
<i class="iconfont icon-bofang"></i>
</div>
<h3>Redmi 10X系列发布会</h3>
</li>
</ul>
</div>
<div id="list">
<ul class="list-warp">
<li data-url="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7cdabcaa763392c86b944eaf4e68d6a3.mp4"
data-title="小米10 青春版 发布会">
<div>
<img src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/96563e75833ba4563bd469dd28203b09.jpg?thumb=1&w=296&h=180&f=webp&q=90"
alt="">
<i class="iconfont icon-bofang"></i>
</div>
<h3>小米10 青春版 发布会</h3>
</li>
</ul>
</div>
<div id="list">
<ul class="list-warp">
<li data-url="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/e25d81c4922fca5ebe51877717ef9b76.mp4"
data-title="小米10 8K手机拍大片">
<div>
<img src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/2fd26bb99b723337a2f8eaba84f7d5bb.jpg?thumb=1&w=296&h=180&f=webp&q=90"
alt="">
<i class="iconfont icon-bofang"></i>
</div>
<h3>小米10 8K手机拍大片</h3>
</li>
</ul>
</div>
</body>
</html>
components/popup
popup.ts
//import './popup.css';//全局css操作
let styles = require('./popup.css');//node模块化的写法引入
import styles1 from './popup.css';//要写对应的声明文件
//配置参数的接口
interface Ipopup {
width?: string;
height?: string;
title?: string;
pos?: string;
mask?: boolean;
content?: (content:HTMLElement) => void;
}
interface Icomponent {
tempContainer: HTMLElement;
init: () => void;
template: () => void;
handle: () => void;
}
//创建function函数
function popup(options: Ipopup) {
return new Popup(options);
}
//声明类
class Popup implements Icomponent {
tempContainer;
mask;
constructor(private settings: Ipopup) {
this.settings = Object.assign({
//添加默认值
width: '100%',
height: '100%',
title: '',
pos: 'center',
mask: true,
content: function () { }
}, this.settings)
this.init();
}
//初始化
init() {
this.template();
this.settings.mask && this.createMask();
this.handle();
this.contentCallBack();
}
//创建模板
template() {
this.tempContainer = document.createElement('div');
this.tempContainer.style.width = this.settings.width;
this.tempContainer.style.height = this.settings.height;
//实现popup样式
this.tempContainer.className = styles['default'].popup;
// this.tempContainer.innerHTML= `<h1 class="popup">hello</h1>`;
// this.tempContainer.innerHTML = `<h1 class="${styles['default'].popup}">hello</h1>`;
this.tempContainer.innerHTML = `
<div class="${styles['default']['popup-title']}">
<h3>${this.settings.title}</h3>
<i class="iconfont icon-guanbi"></i>
</div>
<div class="${styles['default']['popup-content']}"></div>
`;
//将tempContainer的内容添加到body上
document.body.appendChild(this.tempContainer);
//dom操作:弹层左/右/中间/的位置
if (this.settings.pos === 'left') {
this.tempContainer.style.left = 0;
this.tempContainer.style.top = (window.innerHeight - this.tempContainer.offsetHeight) + 'px';
} else if (this.settings.pos === 'right') {
this.tempContainer.style.left = (window.innerWidth - this.tempContainer.offsetWidth) + 'px';
this.tempContainer.style.top = 0;
} else {
this.tempContainer.style.left = (window.innerWidth - this.tempContainer.offsetWidth) / 2 + 'px';
this.tempContainer.style.top = (window.innerHeight - this.tempContainer.offsetHeight) / 2 + 'px';
}
}
//事件操作
handle() {
//获取关闭弹层按钮
let popupClose =this.tempContainer.querySelector(`div.${styles['default']['popup-title']}`).getElementsByTagName('i');
popupClose[0].addEventListener('click',()=>{
document.body.removeChild(this.tempContainer);
this.settings.mask && document.body.removeChild(this.mask);
})
}
//添加遮罩层方法
createMask(){
this.mask = document.createElement('div');
this.mask.className=styles['default'].mask;
this.mask.style.width='100%';
this.mask.style.height=document.body.offsetHeight +'px';
document.body.appendChild(this.mask);
}
//内容回调
contentCallBack(){
let popupContent = this.tempContainer.querySelector(`div.${styles['default']['popup-content']}`);
this.settings.content(popupContent)
}
}
export default popup
popup.css
.popup{position:fixed; z-index:20;border: 1px #ccc solid;}
.popup-title{height: 60px;background:#f5f5f5;display:flex;justify-content:space-between;align-items:center;}
.popup-title h3{font-size:18px;margin-left:20px;}
.popup-title i{font-size:18px;margin-right:20px;cursor: pointer;}
.popup-content{height:calc(100% - 60px);background:white;}
.mask{position: absolute;left: 0;top:0;z-index: 10;background:rgba(0,0,0,0.5);}
components/video
video.js
let styles = require('./video.css');
//接口
interface Ivideo {
url: string;
elem: string | HTMLElement;
width?: string;
height?: string;
autoplay?: boolean;
}
//组件接口
interface Icomponent {
tempContainer: HTMLElement;
init: () => void;
template: () => void;
handle: () => void;
}
function video(options: Ivideo) {
return new Video(options);
}
class Video implements Icomponent {
tempContainer;
constructor(private setting: Ivideo) {
this.setting = Object.assign({
with: '100%',
height: '100%',
autoplay: false
}, this.setting)
this.init();
}
init() {
this.template();
this.handle();
}
template() {
this.tempContainer = document.createElement('div');
this.tempContainer.className = styles['default'].video;
this.tempContainer.style.width = this.setting.width;
this.tempContainer.style.height = this.setting.height;
this.tempContainer.innerHTML = `
<video class ="${styles['default']['video-content']}" src="${this.setting.url}"></video>
<div class="${styles['default']['video-controls']}">
<div class="${styles['default']['video-progress']}">
<div class="${styles['default']['video-progress-now']}"></div>
<div class="${styles['default']['video-progress-suc']}"></div>
<div class="${styles['default']['video-progress-bar']}"></div>
</div>
<div class="${styles['default']['video-play']}">
<i class="iconfont icon-bofang"></i>
</div>
<div class="${styles['default']['video-time']}">
<span>00:00</span>/<span>00:00</span>
</div>
<div class="${styles['default']['video-full']}">
<i class="iconfont icon-quanpingzuidahua"></i>
</div>
<div class="${styles['default']['video-volume']}">
<i class="iconfont icon-yinliang"></i>
<div class="${styles['default']['video-volprogress']}">
<div class="${styles['default']['video-volprogress-now']}"></div>
<div class="${styles['default']['video-volprogress-bar']}"></div>
</div>
</div>
</div>
`;
if (typeof this.setting.elem === 'object') {
this.setting.elem.appendChild(this.tempContainer);
} else {
document.querySelector(`${this.setting.elem}`).appendChild(this.tempContainer);
}
}
handle() {
let videoContent: HTMLVideoElement = this.tempContainer.querySelector(`.${styles['default']['video-content']}`);
let videoControls = this.tempContainer.querySelector(`.${styles['default']['video-controls']}`);
let videoPlay = this.tempContainer.querySelector(`.${styles['default']['video-play']}`).getElementsByTagName('i');
let videoTimes = this.tempContainer.querySelector(`.${styles['default']['video-time']}`).getElementsByTagName('span');
let videoFull = this.tempContainer.querySelector(`div.${styles['default']['video-full']}`).getElementsByTagName('i');
let videoProgess = this.tempContainer.querySelector(`div.${styles['default']['video-progress']}`).getElementsByTagName('div');
let videoVolProgess = this.tempContainer.querySelector(`div.${styles['default']['video-volprogress']}`).getElementsByTagName('div');
let timer;
//音量默认初始值是一半
videoContent.volume = 0.5;
//自动播放 ok
if (this.setting.autoplay) {
timer = setInterval(playing, 1000);
videoContent.play();
}
//控件隐藏 ok
this.tempContainer.addEventListener('mouseenter', function () {
videoControls.style.bottom = 0;
});
this.tempContainer.addEventListener('mouseleave', function () {
videoControls.style.bottom = "-50px";
});
//视频是否加载完毕 ok
videoContent.addEventListener('canplay', () => {
videoTimes[1].innerHTML = formatTime(videoContent.duration);
});
//视频播放事件 ok
videoContent.addEventListener('play', () => {
videoPlay[0].className = 'iconfont icon-zanting';
timer = setInterval(playing, 1000);
});
//视频暂停事件 ok
videoContent.addEventListener('pause', () => {
videoPlay[0].className = 'iconfont icon-bofang';
clearInterval(timer);
});
//播放暂停事件 ok
videoPlay[0].addEventListener('click', () => {
videoContent.paused ? videoContent.play() : videoContent.pause();
});
//简单处理时间格式 ok
function formatTime(number: number): string {
number = Math.round(number);
let min = Math.floor(number / 60);
let sec = number % 60;
return setZero(min) + ':' + setZero(sec);
}
function setZero(number: number): string {
if (number < 10) {
return '0' + number;
} else {
return '' + number;
}
}
//正在播放中-进度条 ok
function playing() {
let scale = videoContent.currentTime / videoContent.duration;
//缓存结点时间
let scaleSuc = videoContent.buffered.end(0) / videoContent.duration;
videoTimes[0].innerHTML = formatTime(videoContent.currentTime);
videoProgess[0].style.width = scale * 100 + '%';
videoProgess[1].style.width = scaleSuc * 100 + '%';
videoProgess[2].style.left = scale * 100 + '%';
}
//全屏处理 ok
videoFull[0].addEventListener('click', () => {
videoContent.requestFullscreen();
});
//拖动功能 ok
videoProgess[2].addEventListener('mousedown', function (ev: MouseEvent) {
let downX = ev.pageX;
let downL = this.offsetLeft;
document.onmousemove = (ev: MouseEvent) => {
let scale = (ev.pageX - downX + downL + 8) / this.parentNode.offsetWidth;
if (scale < 0) {
scale = 0;
} else if (scale > 1) {
scale = 1;
}
videoProgess[0].style.width = scale * 100 + '%';
//缓存
videoProgess[1].style.width = scale * 100 + '%';
this.style.left = scale * 100 + '%';
videoContent.currentTime = scale * videoContent.duration;
};
document.onmouseup = () => {
document.onmousemove = document.onmouseup = null;
};
ev.preventDefault();
});
//音量拖拽功能
videoVolProgess[1].addEventListener('mousedown', function (ev: MouseEvent) {
let downX = ev.pageX;
let downL = this.offsetLeft;
document.onmousemove = (ev: MouseEvent) => {
let scale = (ev.pageX - downX + downL + 8) / this.parentNode.offsetWidth;
if (scale < 0) {
scale = 0;
} else if (scale > 1) {
scale = 1;
}
videoVolProgess[0].style.width = scale * 100 + '%';
this.style.left = scale * 100 + '%';
videoContent.volume = scale;
};
document.onmouseup = () => {
document.onmousemove = document.onmouseup = null;
};
ev.preventDefault();
});
}
}
export default video;
video.css
.video{position: relative;overflow: hidden;}
.video-content{width:100%;height:100%;object-fit: cover;}
.video-controls{bottom: -50px; transition: 0.5s; width:100%;position:absolute;bottom:0;left:0;height:50px;background:rgba(0,0,0,.5);}
.video-progress{position:relative;width:100%;height:5px;background: #222223;}
.video-progress-now{width:0;height: 100%;background:#ff6a03;position: absolute;left:0;z-index: 1;}
.video-progress-suc{position:absolute;left:0;width:0;height: 100%;background:#666;}
.video-progress-bar{height:14px;width: 14px;background:white;border-radius: 50%;position: absolute;left: 0;top: 0;margin-left:-7px;margin-top:-4px; z-index: 2;}
.video-play{float:left;height:45px;line-height:45px;margin-left:35px;}
.video-play i{color:white;font-size:20px;cursor:pointer;}
.video-time{float:left;height:45px;line-height:45px;margin-right:30px;color:white;padding-left: 10px;}
.video-full{float:right;height:45px;line-height:45px;margin-right:20px;}
.video-full i{color:white; font-size: 20px;margin-left: 20px;cursor:pointer;}
.video-volume{float:right;height:45px;display:flex;align-items: center;margin-right: 30px;}
.video-volume i{font-size:20px;color:white;height: 20px; margin-right: 20px;}
.video-volprogress{width:100%;height: 5px;background:#222223;position:relative;}
.video-volprogress-now{width:50px;height:100%;background:#ff6a03;margin-right: 30px;}
.video-volprogress-bar{height:14px;width:14px;background:white;border-radius: 50%;position:absolute;left: 50%;top:0;margin-left:-7px;margin-top:-4px; z-index: 2;}
其它:iconfont/package/webpack.config.js/tsconfig.js等此处忽略,自行配置。另外,可自行多查阅video相关的属性样式以及方法的使用。
网友评论