美文网首页
bunny笔记|Typescript实现video组件封装

bunny笔记|Typescript实现video组件封装

作者: 一只小小小bunny | 来源:发表于2022-04-15 15:16 被阅读0次

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

小米官网视频.png

了解项目需求要点

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
    1. 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 开发环境的运行,确保没影响

  1. 主要是要修改生产环境,如: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相关的属性样式以及方法的使用。

相关文章

  • bunny笔记|Typescript实现video组件封装

    实现小米官网视频组件用typescript封装模块 了解项目需求要点 webpack的安装和使用(1)创建webp...

  • 面向对象-组件

    代码地址 封装一个轮播组件 我的实现 封装一个曝光加载组件 我的实现 封装一个 Tab 组件 我的实现 封装一个 ...

  • 面向对象组件封装

    1.封装一个轮播组件 实现代码 2.封装一个曝光加载组件 实现代码 3.封装一个Tab组件 实现代码 4.封装一个...

  • 用js实现offset方法

    本来前段时间在整理关于vue组件封装、bind实现、promise实现的文章。但是由于篇幅有点大,并且笔记有些久远...

  • 77.vue的属性透传 && 两个component接收同样的p

    1. 如何实现组件的属性透传 场景: 封装 Element-ui组件,从封装组件外部向 Element-ui 组件...

  • React native 第三方组件 React-native-

    简介 这个组件封装了React-Native平台的播放器组件Video 使用方法: 1.安装: 执行命令: 重新编...

  • 组件

    笔记: 组件(component):组件化开发,组件可以扩展HTML元素,封装可重用的代码。全局组件局部组件注:组...

  • vue-video-player组件封装

    首先要在vue项目里安装依赖 在main.js中引入 在components加一个vue组件 在你所需要的页面里面...

  • Vue 子传父 表单控件二次封装

    需求:实现上图表单,封装成组件实现过程:1.子组件:Child 2.父组件使用Child组件 实战: 子组件:Re...

  • bunny笔记|Typescript基础理解与应用

    01-ts环境 强类型语言弱类型语言 动态类型静态类型 配置环境新建文件夹,vscode打开文件夹,打开终端初始化...

网友评论

      本文标题:bunny笔记|Typescript实现video组件封装

      本文链接:https://www.haomeiwen.com/subject/xtnrsrtx.html