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

这篇具有很好参考价值的文章主要介绍了vue3+ts实现视频根据时间轴截取,并可以通过传入截取起止时间进行当前剪辑的回显。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

穿插一个简化版本,时间轴是一条线,功能比这个简化,或许会符合部分人的需求。

链接vue3+ts实现视频根据时间轴截取_vue项目截取一段正在播放的视频_一个人的咖啡~的博客-CSDN博客

功能概述

通过传入源视频时长,源视频的视频地址,当前剪辑的开始时间,当前剪辑的结束时间和关键帧缩略图(需要20张图片,后端提供,根据视频时长分为20节,每节取一张图)五个必传参数,视频地址将通过video标签播放,组件尺寸为100%,根据父级组件的宽度自动撑满。

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

参数描述
endTime 视频结束时间,精确到毫秒
url 视频地址,将通过video标签展示
spliterStartTime 视频截取开始时间
spliterEndTime 视频截取结束时间
photoList 时间轴缩略图列表
回调描述
回调方法 回调参数(形参) 参数描述
queryTime Array [开始时间,结束时间]

template部分

<template>
	<video id="videoPlayer" @play="onplay" controls="true" preload="auto" muted class="video" width="100%"
		:src="props.url"></video>
	<ul class="time-list">
		<li v-for="item in data.timeList" :key="item">{{item}}</li>
	</ul>
	<div class="crop-filter">
		<div class="timer-shaft" ref="shaft">
			<div class="white-shade" :style="{width:(data.endLeft-data.startLeft+12)+'px',left:data.startLeft-6+'px'}">
			</div>
			<div class="left-shade" :style="{width: (data.startLeft-6)+'px'}"></div>
			<div class="right-shade" :style="{width: (shaft?.clientWidth-data.endLeft-6) +'px'}"></div>
			<div class="strat-circle circle" ref="start" @mousedown="startMouseDown">
				<div class="center"></div>
			</div>
			<div class="end-circle circle" ref="end" @mousedown="endMouseDown">
				<div class="center"></div>
			</div>
			<!-- 此处src应绑定item -->
			<img @dragstart.prevent style="width: 5%;user-select: none;" v-for="item in props.photoList"
				src="../../../public/favicon.ico" alt="">
		</div>
	</div>
</template>

分为三个部分,上面是video标签,中间是根据总时长处理出的时间数组,下面是时间轴。文章来源地址https://www.toymoban.com/news/detail-408490.html

script部分

<!-- 起止时间间隔最小≈1秒 -->
<script setup lang="ts">
	import {
		getNowTime,
		dateStrChangeTimeTamp,
		cropFilter,
		videoRef,
	} from '@/types/type'
	// 进度条dom
	const shaft = ref(null);
	// 开始按钮dom
	const start = ref(null);
	// 结束按钮dom
	const end = ref(null);
	const data = reactive(new cropFilter)
	// props参数类型
	interface Props {
		startTime ? : string;
		endTime: string;
		url: string;
		spliterStartTime ? : string;
		spliterEndTime: string;
		// 此处为模拟
		photoList: string[];
	}
	// 设置默认值,需要显式的开启,具体查看vue3文档
	const props = withDefaults(defineProps < Props > (), {
		startTime: '00:00:00.0',
		endTime: '00:00:08.0',
		spliterStartTime: '00:00:00.0',
		spliterEndTime: '00:00:08.0',
		url: '',
		photoList: [],
	})
	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.clientWidth
		// 结束毫秒数
		let endM = (dateStrChangeTimeTamp('1970-01-02 ' + props?.spliterEndTime) - (1000 * 60 * 60 * 16))
		// 开始毫秒数
		let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (props?.spliterStartTime)) - (1000 * 60 * 60 * 16))
		console.log(startM, endM)
		// 设置开始结束位置
		start.value.style.left = startM / data.roal - (end.value.clientWidth / 2) + 'px'
		end.value.style.left = endM / data.roal - (end.value.clientWidth / 2) + 'px'

		data.endLeft = end.value.offsetLeft
		data.endright = shaft.value.clientWidth - (end.value.clientWidth / 2)
		data.startLeft = start.value.offsetLeft + (start.value.clientWidth / 2)
		getVideoTime()
		data.timeList.push(props.startTime)
		let paragraph = (dateStrChangeTimeTamp(str + props.endTime) - (1000 * 60 * 60 * 16)) / 5
		for (let i = 1; i < 6; i++) {
			data.timeList.push(getNowTime(paragraph * i))
		}
	})
	// 播放事件
	const onplay = () => {
		let myVideo: videoRef = document.getElementById('videoPlayer');
		// 开始秒数
		let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props
			.spliterStartTime)) - (1000 * 60 * 60 * 16)) / 1000
		// 结束秒数
		let endM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.endTime ? data.endTime : props
			.spliterEndTime)) - (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
					.spliterEndTime)) - (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 *  Math.floor(start.value.offsetLeft + (start.value.clientWidth /
				2)))
			data.startLeft = start.value.clientWidth + start.value.offsetLeft
			// 开始秒数
			let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props
				.spliterStartTime)) - (1000 * 60 * 60 * 16)) / 1000
			playBySeconds(startM)
		};
		document.onmouseup = (e) => {
			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 *  Math.floor(end.value.offsetLeft + (end.value.clientWidth / 2)))
			data.endLeft = end.value.offsetLeft
		};
		document.onmouseup = (e) => {
			document.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;
	}

	.time-list {
		width: 100%;
		color: #C0C0C0;
		font-size: 0.12rem;
		margin-bottom: 0.1rem;
		display: flex;
		align-items: center;
		justify-content: space-between;
	}

	.crop-filter {
		width: 100%;
		padding: 0 0.1rem;
		box-sizing: border-box;
		display: flex;
		align-items: center;

		.timer-shaft {
			width: 100%;
			position: relative;

			.circle {
				width: 0.2rem;
				position: absolute;
				top: -8%;
				height: 110%;
				background-color: #ffffff;
				cursor: e-resize;
				display: flex;
				align-items: center;
				justify-content: center;

				.center {
					width: 0.02rem;
					height: 0.15rem;
					background-color: #D8D8D8;
				}
			}

			.strat-circle {
				left: -0.09rem;
				border-radius: 0.03rem 0 0 0.03rem;
			}

			.end-circle {
				right: -0.1rem;
				border-radius: 0 0.03rem 0.03rem 0;
			}

			.white-shade {
				position: absolute;
				top: -8%;
				height: 110%;
				width: 100%;
				background-color: transparent;
				border: 0.04rem solid #fff;
				box-sizing: border-box;
				border-left: 0;
				border-right: 0;
			}

			.left-shade {
				position: absolute;
				left: 0;
				top: 0;
				height: 100%;
				background: rgba(0, 0, 0, 0.6);
			}

			.right-shade {
				position: absolute;
				right: 0;
				top: 0;
				height: 100%;
				background: rgba(0, 0, 0, 0.6);
			}
		}
	}
</style>

.vue完整代码

<template>
	<video id="videoPlayer" @play="onplay" controls="true" preload="auto" muted class="video" width="100%"
		:src="props.url"></video>
	<ul class="time-list">
		<li v-for="item in data.timeList" :key="item">{{item}}</li>
	</ul>
	<div class="crop-filter">
		<div class="timer-shaft" ref="shaft">
			<div class="white-shade" :style="{width:(data.endLeft-data.startLeft+12)+'px',left:data.startLeft-6+'px'}">
			</div>
			<div class="left-shade" :style="{width: (data.startLeft-6)+'px'}"></div>
			<div class="right-shade" :style="{width: (shaft?.clientWidth-data.endLeft-6) +'px'}"></div>
			<div class="strat-circle circle" ref="start" @mousedown="startMouseDown">
				<div class="center"></div>
			</div>
			<div class="end-circle circle" ref="end" @mousedown="endMouseDown">
				<div class="center"></div>
			</div>
			<!-- 此处src应绑定item -->
			<img @dragstart.prevent style="width: 5%;user-select: none;" v-for="item in props.photoList"
				src="../../../public/favicon.ico" alt="">
		</div>
	</div>
</template>
<!-- 起止时间间隔最小≈1秒 -->
<script setup lang="ts">
	import {
		getNowTime,
		dateStrChangeTimeTamp,
		cropFilter,
		videoRef,
	} from '@/types/type'
	// 进度条dom
	const shaft = ref(null);
	// 开始按钮dom
	const start = ref(null);
	// 结束按钮dom
	const end = ref(null);
	const data = reactive(new cropFilter)
	// props参数类型
	interface Props {
		startTime ? : string;
		endTime: string;
		url: string;
		spliterStartTime ? : string;
		spliterEndTime: string;
		// 此处为模拟
		photoList: string[];
	}
	// 设置默认值,需要显式的开启,具体查看vue3文档
	const props = withDefaults(defineProps < Props > (), {
		startTime: '00:00:00.0',
		endTime: '00:00:08.0',
		spliterStartTime: '00:00:00.0',
		spliterEndTime: '00:00:08.0',
		url: '',
		photoList: [],
	})
	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.clientWidth
		// 结束毫秒数
		let endM = (dateStrChangeTimeTamp('1970-01-02 ' + props?.spliterEndTime) - (1000 * 60 * 60 * 16))
		// 开始毫秒数
		let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (props?.spliterStartTime)) - (1000 * 60 * 60 * 16))
		console.log(startM, endM)
		// 设置开始结束位置
		start.value.style.left = startM / data.roal - (end.value.clientWidth / 2) + 'px'
		end.value.style.left = endM / data.roal - (end.value.clientWidth / 2) + 'px'

		data.endLeft = end.value.offsetLeft
		data.endright = shaft.value.clientWidth - (end.value.clientWidth / 2)
		data.startLeft = start.value.offsetLeft + (start.value.clientWidth / 2)
		getVideoTime()
		data.timeList.push(props.startTime)
		let paragraph = (dateStrChangeTimeTamp(str + props.endTime) - (1000 * 60 * 60 * 16)) / 5
		for (let i = 1; i < 6; i++) {
			data.timeList.push(getNowTime(paragraph * i))
		}
	})
	// 播放事件
	const onplay = () => {
		let myVideo: videoRef = document.getElementById('videoPlayer');
		// 开始秒数
		let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props
			.spliterStartTime)) - (1000 * 60 * 60 * 16)) / 1000
		// 结束秒数
		let endM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.endTime ? data.endTime : props
			.spliterEndTime)) - (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
					.spliterEndTime)) - (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 *  Math.floor(start.value.offsetLeft + (start.value.clientWidth /
				2)))
			data.startLeft = start.value.clientWidth + start.value.offsetLeft
			// 开始秒数
			let startM = (dateStrChangeTimeTamp('1970-01-02 ' + (data.startTime ? data.startTime : props
				.spliterStartTime)) - (1000 * 60 * 60 * 16)) / 1000
			playBySeconds(startM)
		};
		document.onmouseup = (e) => {
			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 *  Math.floor(end.value.offsetLeft + (end.value.clientWidth / 2)))
			data.endLeft = end.value.offsetLeft
		};
		document.onmouseup = (e) => {
			document.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;
	}

	.time-list {
		width: 100%;
		color: #C0C0C0;
		font-size: 0.12rem;
		margin-bottom: 0.1rem;
		display: flex;
		align-items: center;
		justify-content: space-between;
	}

	.crop-filter {
		width: 100%;
		padding: 0 0.1rem;
		box-sizing: border-box;
		display: flex;
		align-items: center;

		.timer-shaft {
			width: 100%;
			position: relative;

			.circle {
				width: 0.2rem;
				position: absolute;
				top: -8%;
				height: 110%;
				background-color: #ffffff;
				cursor: e-resize;
				display: flex;
				align-items: center;
				justify-content: center;

				.center {
					width: 0.02rem;
					height: 0.15rem;
					background-color: #D8D8D8;
				}
			}

			.strat-circle {
				left: -0.09rem;
				border-radius: 0.03rem 0 0 0.03rem;
			}

			.end-circle {
				right: -0.1rem;
				border-radius: 0 0.03rem 0.03rem 0;
			}

			.white-shade {
				position: absolute;
				top: -8%;
				height: 110%;
				width: 100%;
				background-color: transparent;
				border: 0.04rem solid #fff;
				box-sizing: border-box;
				border-left: 0;
				border-right: 0;
			}

			.left-shade {
				position: absolute;
				left: 0;
				top: 0;
				height: 100%;
				background: rgba(0, 0, 0, 0.6);
			}

			.right-shade {
				position: absolute;
				right: 0;
				top: 0;
				height: 100%;
				background: rgba(0, 0, 0, 0.6);
			}
		}
	}
</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;
	// 时间轴显示时间数组
	timeList: string[] = [];
}

//日期字符串转成时间戳
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() - 8
	const 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 + '.' + milliSeconds
	console.log(currentTime)
	return currentTime
}

到了这里,关于vue3+ts实现视频根据时间轴截取,并可以通过传入截取起止时间进行当前剪辑的回显的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端开发攻略---Vue通过自定义指令实现元素平滑上升的动画效果(可以自定义动画时间、动画效果、动画速度等等)。

    这个指令不是原生自带的,需要手动去书写,但是这辈子只需要编写这一次就好了,后边可以反复利用。 IntersectionObserver 是一个用于监测元素是否进入或离开视口(viewport)的 API。它可以帮助你在页面滚动时或者元素位置改变时进行回调操作,这样你就可以根据元素是否可见

    2024年04月11日
    浏览(41)
  • vue3+vite+ts视频背景酷炫登录模板【英雄联盟主题】

    最近我准备在自己的网站上开发一个博客系统,首先要实现后台登录界面。我选择使用Vue 3 + Vite + TypeScript框架来构建,下面是针对该主题的详细说明: 在网页中使用视频作为背景图已经相当常见了,而且网上也有很多相关的插件可供使用。我选择使用\\\"vue-responsive-video-backgr

    2024年02月15日
    浏览(35)
  • js(ts)截取视频第一帧作为封面图

    直接上代码: 这里有三个地方需要注意: 1. 需要加上 preload 属性 这是防止截图结果为黑屏的关键一步 2. canvas宽高的设置 网络上其它文章的代码都直接读取 video.width 和 video.height ,会导致如果是竖视频截取出来的封面图在横显示时会变形 3. 判断图片有效性 有时候截取到的图

    2024年02月10日
    浏览(29)
  • vue实现截取视频第一帧作为封面

    在Vue中实现截取视频第一帧作为视频封面可以通过以下步骤: 在Vue组件中引入video.js和videojs-contrib-hls插件。 在Vue组件中声明一个video元素,并给其添加id。 在Vue组件中初始化video.js插件,并监听loadedmetadata事件。 编写captureVideoThumbnail方法来截取视频第一帧。 在Vue组件中使用

    2024年02月12日
    浏览(31)
  • Vue3:通过路由写多个页面,通过不同的路径可以进入不同的页面

    Vue3:想通过路由写2个页面,不同的路径可以进入不同的页面 1、创建Vue3项目 通过脚手架创建一个Vue3的项目,然后在此基础上对文件进行增删改,修改成自己需要的项目框架 2、views文件夹 对应 页面文件 如果需要写2个页面,则在views文件夹里面创建2个.vue文件 可以在.vue文件

    2024年02月11日
    浏览(31)
  • vue3+ts 实现枚举

    首先 index.ts 中定义枚举 接口返给的数据是一个对象

    2024年02月14日
    浏览(32)
  • 【新项目开发】vue3+ts+elementPlus+ffmpegjs开发纯web端的视频编辑器

    当在项目中使用新技术时,我们应该首先进行调研,了解其特点和使用方法。在实现功能时,我们可以采用最简单的方式,而不必过于关注项目的设计和结构。一旦掌握了新技术,我们可以根据其API属性进行代码设计,以便更好地开发。以开发一个纯web端的视频编辑处理器为

    2024年02月15日
    浏览(26)
  • vue2实现截取视频第一帧图像作为开始封面

    要在Vue中实现截取视频的第一帧作为封面,你可以使用HTML5的元素和Canvas API来实现。loadeddata事件监听必须在视频加载完成后执行,然后通过Canvas的drawImage方法将视频帧绘制到Canvas上,并使用toDataURL方法将Canvas内容转换为Base64编码的图像数据URL 在你的Vue组件中,使用元素来加

    2024年02月12日
    浏览(22)
  • Vue3+Ts:实现paypal按钮

    前言:到了让我激动人心的时刻,paypal支付按钮的前端处理(唯一不足之处是,没有后端处理,有时间我研究下) 先放代码如何实现,再深研究一下逻辑。 第一步:下载paypal.js依赖 第二步:引入要使用的vue页面,并调用。 依赖地址 其实上面这些就已经能满足操作了,但是

    2024年01月23日
    浏览(42)
  • 前端开发小技巧 - 【Vue3 + TS】 - 在 TS + Vue3 中使用 Pinia,实现 Pinia 的持久化,优化Pinia(仓库统一管理)

    ts 中使用 pinia 和 Vue3 基本一致,唯一的不同点在于,需要根据接口文档给 state 标注类型,也要给 actions 标注类型; 以下都是 组合式API 的写法, 选项式API 的写法大家可以去官网看看; Pinia; 持久化插件 - pinia-plugin-persistedstate; 目标文件: src/types/user.d.ts (这里以 user.d.t

    2024年04月09日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包