公司提出想做一个视频编辑功能,每次只裁剪一段即可,从网上也没找到合适的组件,简单思考后觉得并不难,决定自己封装一个吧。组件涉及到的只有vue3+ts+scss,没有使用其他插件。

基于目前这版升级版本,可以实现时间轴为关键帧图片列表,并拖动截取,选中的地方高亮,未选中的变灰,并可以通过传入截取起止时间回显上次裁剪位置。

链接https://blog.csdn.net/wed2019/article/details/126995825

功能概述

通过传入开始时间,结束时间,视频地址三个必传参数,视频地址将通过video标签播放,组件尺寸为100%,根据父级组件的宽度自动撑满。

时间轴模块,会根据传入的起止时间自动换算出1px===毫秒数,起止时间间隔我设置了1秒以上,开始时间拖动到结束时间前一秒左右将停止移动,结束时间拖动到开始时间后一秒左右将无法拖动,设置开始时间后会自动将video标签的开始播放时间定位到截取的开始时间,设置结束时间后,video播放到截取的结束时间后会自动暂停,这时video标签将只能播放所截取的起止时间范围的视频。最后设置了回调queryTime(),通过回调将起止时间传出,我的业务中视频截取是后端操作,前端只需要提供起止时间即可,具体看代码,如下:

参数描述
startTime 视频开始时间,精确到毫秒(如:00:00:00.0)
endTime 视频结束时间,精确到毫秒
url 视频地址,将通过video标签展示
回调描述
回调方法 回调参数(形参) 参数描述
queryTime Array [开始时间,结束时间]

template部分

<template><video id="videoPlayer" @play="onplay" controls="true" preload="auto" muted class="video" width="100%":src="props.url"></video><div class="crop-filter"><div class="timer-shaft" ref="shaft"><div class="strat-circle circle" ref="start" @mousedown="startMouseDown"><div class="timer">{{ data.startTime||props.startTime}}</div></div><div class="end-circle circle" ref="end" @mousedown="endMouseDown"><div class="timer">{{data.endTime||props.endTime}}</div></div></div></div>
</template>

分为两个部分,上面是video标签,下面是时间轴。

script部分

<!-- 起止时间间隔最小≈1秒 -->
<script setup lang="ts">import {getNowTime,dateStrChangeTimeTamp,cropFilter,videoRef,} from '@/types/type'// 进度条domconst shaft = ref(null);// 开始按钮domconst start = ref(null);// 结束按钮domconst end = ref(null);const data = reactive(new cropFilter)// props参数类型interface Props {startTime: string;endTime: string;url: string;}// 设置默认值,需要显式的开启,具体查看vue3文档const props = withDefaults(defineProps < Props > (), {startTime: '00:00:00.0',endTime: '00:00:08.0',url: '',})const emit = defineEmits(['queryTime'])onMounted(() => {// 1970年的年月日字符串+' 'let str = '1970-01-02 'let time = dateStrChangeTimeTamp(str + props.endTime) - dateStrChangeTimeTamp(str + props.startTime)data.roal = time / shaft.value.clientWidthgetNowTime(time)data.endLeft = end.value.offsetLeftdata.endright = end.value.offsetLeftdata.startLeft = start.value.clientWidth - (start.value.clientWidth / 2)getVideoTime()})// 播放事件const onplay = () => {let myVideo: videoRef = document.getElementById('videoPlayer');// 开始秒数let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props.startTime)) - (1000 * 60 * 60 * 16)) / 1000// 结束秒数let endM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.endTime ? data.endTime : props.endTime)) - (1000 * 60 * 60 * 16)) / 1000// 如果当前秒数小于等于截取的开始时间,就按截取的开始时间播放,如果不是,则为继续播放if (myVideo.currentTime <= startM || myVideo.currentTime > endM) {myVideo.currentTime = startM;myVideo.play();}}// 获取视频播放时长const getVideoTime = () => {if (document.getElementById('videoPlayer')) {let videoPlayer: videoRef = document.getElementById('videoPlayer');videoPlayer.addEventListener('timeupdate', function() {// 结束秒数let endM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.endTime ? data.endTime : props.endTime)) - (1000 * 60 * 60 * 16)) / 1000// 如果当前播放时间大于等于截取的结束秒数,就暂停if (videoPlayer.currentTime >= endM) {videoPlayer.pause()}}, false)}}//设置播放点const playBySeconds = (num: number) => {if (num && document.getElementById('videoPlayer')) {let myVideo: videoRef = document.getElementById('videoPlayer');myVideo.currentTime = num;}}// 起始按钮const startMouseDown = (e) => {let odiv = e.currentTarget; //获取目标父元素//算出鼠标相对元素的位置let disX = e.clientX - odiv.offsetLeft;document.onmousemove = (e) => { //鼠标按下并移动的事件//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置let left = e.clientX - disX;//移动当前元素odiv.style.left = left + 'px';//获取距离窗口宽度let mas = odiv.offsetLeft;if (mas <= -(start.value.clientWidth / 2)) {odiv.style.left = -(start.value.clientWidth / 2) + 'px';} else if (mas >= (data.endLeft - Math.ceil(1000 / data.roal))) {odiv.style.left = (data.endLeft - Math.ceil(1000 / data.roal)) + 'px';}data.startTime = getNowTime(data.roal * (start.value.offsetLeft + (start.value.clientWidth /2)))};document.onmouseup = (e) => {data.startLeft = start.value.clientWidth + start.value.offsetLeft// 开始秒数let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props.startTime)) - (1000 * 60 * 60 * 16)) / 1000playBySeconds(startM)document.onmousemove = null;document.onmouseup = null;handleTime()};}// 结束按钮const endMouseDown = (e) => {let odiv = e.currentTarget; //获取目标父元素//算出鼠标相对元素的位置let disX = e.clientX - odiv.offsetLeft;document.onmousemove = (e) => { //鼠标按下并移动的事件//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置let left = e.clientX - disX;//移动当前元素odiv.style.left = left + 'px';//获取距离窗口宽度let mas = odiv.offsetLeft;if (mas <= (data.startLeft - end.value.clientWidth + Math.ceil(1000 / data.roal))) {odiv.style.left = (data.startLeft - end.value.clientWidth + Math.ceil(1000 / data.roal)) +'px';} else if (mas >= data.endright) {odiv.style.left = data.endright + 'px';}data.endTime = getNowTime(data.roal * (end.value.offsetLeft + (end.value.clientWidth / 2)))};document.onmouseup = (e) => {data.endLeft = end.value.offsetLeftdocument.onmousemove = null;document.onmouseup = null;handleTime()};}// 传出起止时间的回调const handleTime = () => {let arr = [data.startTime, data.endTime]emit('queryTime', arr)}
</script>

css部分

<style scoped lang="scss">.video {width: 100%;margin-bottom: 0.2rem;}.crop-filter {width: 100%;height: 0.5rem;padding: 0 0.4rem;box-sizing: border-box;display: flex;align-items: center;.timer-shaft {width: 100%;border-bottom: 0.04rem solid #56a0ee;border-radius: 0.1rem;position: relative;.circle {width: 0.12rem;height: 0.12rem;border-radius: 50%;position: absolute;top: -0.04rem;background-color: #fff;cursor: pointer;.timer {position: absolute;color: white;font-size: 0.1rem;left: -0.3rem;user-select: none;}}.strat-circle {left: -0.06rem;background-color: #ff14ec;.timer {bottom: -0.15rem;}}.end-circle {right: -0.06rem;.timer {top: -0.15rem;}}}}
</style>

.vue完整代码

<template><video id="videoPlayer" @play="onplay" controls="true" preload="auto" muted class="video" width="100%":src="props.url"></video><div class="crop-filter"><div class="timer-shaft" ref="shaft"><div class="strat-circle circle" ref="start" @mousedown="startMouseDown"><div class="timer">{{ data.startTime||props.startTime}}</div></div><div class="end-circle circle" ref="end" @mousedown="endMouseDown"><div class="timer">{{data.endTime||props.endTime}}</div></div></div></div>
</template>
<!-- 起止时间间隔最小≈1秒 -->
<script setup lang="ts">import {getNowTime,dateStrChangeTimeTamp,cropFilter,videoRef,} from '@/types/type'// 进度条domconst shaft = ref(null);// 开始按钮domconst start = ref(null);// 结束按钮domconst end = ref(null);const data = reactive(new cropFilter)// props参数类型interface Props {startTime: string;endTime: string;url: string;}// 设置默认值,需要显式的开启,具体查看vue3文档const props = withDefaults(defineProps < Props > (), {startTime: '00:00:00.0',endTime: '00:00:08.0',url: '',})const emit = defineEmits(['queryTime'])onMounted(() => {// 1970年的年月日字符串+' 'let str = '1970-01-02 'let time = dateStrChangeTimeTamp(str + props.endTime) - dateStrChangeTimeTamp(str + props.startTime)data.roal = time / shaft.value.clientWidthgetNowTime(time)data.endLeft = end.value.offsetLeftdata.endright = end.value.offsetLeftdata.startLeft = start.value.clientWidth - (start.value.clientWidth / 2)getVideoTime()})// 播放事件const onplay = () => {let myVideo: videoRef = document.getElementById('videoPlayer');// 开始秒数let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props.startTime)) - (1000 * 60 * 60 * 16)) / 1000// 结束秒数let endM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.endTime ? data.endTime : props.endTime)) - (1000 * 60 * 60 * 16)) / 1000// 如果当前秒数小于等于截取的开始时间,就按截取的开始时间播放,如果不是,则为继续播放if (myVideo.currentTime <= startM || myVideo.currentTime > endM) {myVideo.currentTime = startM;myVideo.play();}}// 获取视频播放时长const getVideoTime = () => {if (document.getElementById('videoPlayer')) {let videoPlayer: videoRef = document.getElementById('videoPlayer');videoPlayer.addEventListener('timeupdate', function() {// 结束秒数let endM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.endTime ? data.endTime : props.endTime)) - (1000 * 60 * 60 * 16)) / 1000// 如果当前播放时间大于等于截取的结束秒数,就暂停if (videoPlayer.currentTime >= endM) {videoPlayer.pause()}}, false)}}//设置播放点const playBySeconds = (num: number) => {if (num && document.getElementById('videoPlayer')) {let myVideo: videoRef = document.getElementById('videoPlayer');myVideo.currentTime = num;}}// 起始按钮const startMouseDown = (e) => {let odiv = e.currentTarget; //获取目标父元素//算出鼠标相对元素的位置let disX = e.clientX - odiv.offsetLeft;document.onmousemove = (e) => { //鼠标按下并移动的事件//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置let left = e.clientX - disX;//移动当前元素odiv.style.left = left + 'px';//获取距离窗口宽度let mas = odiv.offsetLeft;if (mas <= -(start.value.clientWidth / 2)) {odiv.style.left = -(start.value.clientWidth / 2) + 'px';} else if (mas >= (data.endLeft - Math.ceil(1000 / data.roal))) {odiv.style.left = (data.endLeft - Math.ceil(1000 / data.roal)) + 'px';}data.startTime = getNowTime(data.roal * (start.value.offsetLeft + (start.value.clientWidth /2)))};document.onmouseup = (e) => {data.startLeft = start.value.clientWidth + start.value.offsetLeft// 开始秒数let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props.startTime)) - (1000 * 60 * 60 * 16)) / 1000playBySeconds(startM)document.onmousemove = null;document.onmouseup = null;handleTime()};}// 结束按钮const endMouseDown = (e) => {let odiv = e.currentTarget; //获取目标父元素//算出鼠标相对元素的位置let disX = e.clientX - odiv.offsetLeft;document.onmousemove = (e) => { //鼠标按下并移动的事件//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置let left = e.clientX - disX;//移动当前元素odiv.style.left = left + 'px';//获取距离窗口宽度let mas = odiv.offsetLeft;if (mas <= (data.startLeft - end.value.clientWidth + Math.ceil(1000 / data.roal))) {odiv.style.left = (data.startLeft - end.value.clientWidth + Math.ceil(1000 / data.roal)) +'px';} else if (mas >= data.endright) {odiv.style.left = data.endright + 'px';}data.endTime = getNowTime(data.roal * (end.value.offsetLeft + (end.value.clientWidth / 2)))};document.onmouseup = (e) => {data.endLeft = end.value.offsetLeftdocument.onmousemove = null;document.onmouseup = null;handleTime()};}// 传出起止时间的回调const handleTime = () => {let arr = [data.startTime, data.endTime]emit('queryTime', arr)}
</script><style scoped lang="scss">.video {width: 100%;margin-bottom: 0.2rem;}.crop-filter {width: 100%;height: 0.5rem;padding: 0 0.4rem;box-sizing: border-box;display: flex;align-items: center;.timer-shaft {width: 100%;border-bottom: 0.04rem solid #56a0ee;border-radius: 0.1rem;position: relative;.circle {width: 0.12rem;height: 0.12rem;border-radius: 50%;position: absolute;top: -0.04rem;background-color: #fff;cursor: pointer;.timer {position: absolute;color: white;font-size: 0.1rem;left: -0.3rem;user-select: none;}}.strat-circle {left: -0.06rem;background-color: #ff14ec;.timer {bottom: -0.15rem;}}.end-circle {right: -0.06rem;.timer {top: -0.15rem;}}}}
</style>

type.ts代码

export interface videoRef {// 其他冗余字段[propName: string]: any;// 数字值,表示当前播放的时间,以秒计currentTime: number;
}
export class cropFilter {// 结束按钮距离左侧距离endLeft: string | number = 0;// 结束按钮初始位置endright: string | number = 0;// 开始按钮距离左侧距离startLeft: string | number = 0;// 毫秒/px(1px===的毫秒数)roal: string | number = 0;// 开始时间startTime: string | number = 0;// 结束时间endTime: string | number = 0;
}//日期字符串转成时间戳
export function dateStrChangeTimeTamp(dateStr: string) {dateStr = dateStr.substring(0, 23);dateStr = dateStr.replace(/-/g, '/');let timeTamp = new Date(dateStr).getTime();return timeTamp
}
// 精准到毫秒
export function getNowTime(val: string | number) {const date = new Date(val)const hour = (date.getHours() - 8) < 10 ? '0' + (date.getHours() - 8) : date.getHours() - 8const minute = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()const second = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()const milliSeconds = date.getMilliseconds() //毫秒const currentTime = hour + ':' + minute + ':' + second + '.' + milliSecondsconsole.log(currentTime)return currentTime
}

vue3+ts实现视频根据时间轴截取相关推荐

  1. vue3+ts实现视频根据时间轴截取,并可以通过传入截取起止时间进行当前剪辑的回显

    公司提出想做一个视频编辑功能,每次只裁剪一段即可,UI同时也想实现时间轴为关键帧图片的效果,从网上也没找到合适的组件,简单思考后觉得并不难,决定自己封装一个吧.组件涉及到的只有vue3+ts+scss ...

  2. 如何在B站视频发送时间轴空降弹幕

    在B站看视频,为了节省时间,我们会选择直接跳过片头,或者直接跳转到精彩的时间位置,俗称空降- 空降的传统做法是,先来的观众,在片头发送正片开始时间的弹幕,后面的观众按照弹幕显示的空降时间,点击视频时间 ...

  3. 怎样才能在自动驾驶任务中高效地利用时间轴上的信息进行视频检测?

    目录 这篇论文究竟想解决什么样的问题? 我们要尊重时间轴上的因果关系 我们对实时性有要求 简单粗暴地只添加LSTM模块行不行? 我们简单地回顾一下LSTM的原理 LSTM的局限性 作者建议的模型是如何 ...

  4. Camtasia实用技巧之时间轴

    通常在录制完视频后我们会对视频进行相关的修改操作,而在修改时,不可避免的要对Camtasia的时间轴进行操作.下面我用Camtasia 2020(Win版)来为大家介绍一些时间轴操作的小技巧. Cam ...

  5. python 视频字幕替换_利用python调整srt字幕时间轴

    我们有时候下载了超高清资源但是没有合适的字幕,往往由于资源的不同,视频和字幕会有几秒钟的时差,这个时候选择重新下载视频显得十分的麻烦. 批量调整字幕时间轴就是一个很好的办法.网上关于调整字幕时间轴有很 ...

  6. vue-time-slot 一款基于vue3的会议预约时间组件,时间轴为早7-晚11,可自由选择,并且非常方便。

    vue-time-slot 这款组件是一个会议预约时间的组件,时间轴为早7-晚11,可自由选择 非常便捷,点击确认后会返回一个时间段,首先我们安装 npm install vue3-time-slot ...

  7. java使用ffmpeg截取视频某个时间点的截图

    1.软件安装 mac brew install ffmpeg 安装后路径:"/usr/local/Cellar/ffmpeg/4.0.1/bin ffmpeg –version 查看版本 l ...

  8. Vue3时间轴(Timeline)

    Vue2时间轴(Timeline) 可自定义设置以下属性: 时间轴内容数组(timelineData),必传,类型:Array<{desc: string, color?: string}> ...

  9. python提取视频字幕_GitHub - jiulinxiri/video-timeline-and-subtitle-extract: 视频时间轴及字幕提取...

    视频时间轴及字幕提取 能帮助你: 1.识别字幕的时间轴 通过帧差法判断是否相同帧, 进而由相同帧得出字幕时间轴 计算时间轴对应帧的 SSIM , 合并相同的时间轴 2.利用OCR识别字幕 将指定字幕区 ...

最新文章

  1. 《AI系统周刊》第2期:硬件支持的Tensor-train分解与高效数据处理、清华吴文斐研究组获得NSDI最佳论文奖
  2. wps android qq 群,手机WPS怎样发送文档给QQ?WPS怎样发送文档给QQ教程
  3. Centos7常用操作
  4. 【Jmeter】 Report Dashboard 生成html图形测试报告
  5. springmvc十八:RestfulCRUD增删改查小实战
  6. PDF页眉页脚怎么设置
  7. BufferFsStream
  8. Xshell调节字体大小和样式
  9. SUN公司经典linux教材转自http://blog.chinaunix.net/uid-20446831-id-1677336.html
  10. SQL中的模糊范围查询特殊符号应用
  11. C# StringBuilder 和 String 的区别?(简单易懂不抽象)
  12. SpringBoot 启动报 Stopping service [Tomcat]
  13. 专利申请“技术交底书”的要素和撰写要求
  14. linux解pdf隐写工具,Linux版PDF解密工具PDFDecryptionTool-Deepin-amd64.deb下载
  15. 锐道发布Dorado Dorado7标准件 -1.0.24 beta版
  16. 【React】React三大属性:Props、State、Refs
  17. 解决WPS每点击一下保存,就会出现tmp文件
  18. 开源三轴云台EVVGC(simple BGC)分析
  19. 部署N9e-v3运维监控平台
  20. 两位数码管秒表c语言,单片机C语言编程实现双数码管可调秒表

热门文章

  1. 西瓜视频上有哪些很好的IT科技财经科普类学习资源?
  2. AD9中怎么建立多个部分的组成的单个器件
  3. 【元胞自动机】基于元胞自动机模拟行人通过斑马线matlab代码
  4. DC-8靶机渗透测试详细教程
  5. CCS软件从零开始新建工程的简易流程
  6. timesten系列二:如何安装timesten
  7. Altium Designer 详细图文教程
  8. 实验2 混频器性能的测试与分析
  9. maven本地仓库路径配置
  10. [OpenGL]射线拾取RayPicking---(1)生成射线