vue.js 视频截取为 gif - 2(将截取到的gif 转换为base64 、file)

作者:改了一个昵称日期:2025/10/25

demo.vue

1<template>
2  <div>
3    <div>
4      <video ref="videoRef" :src="theUrl" controls autoplay muted crossOrigin="anonymous"></video>
5
6      <!-- <div class="controls">
7        <button :disabled="isRecording" @click="startCapture">
8          开始录制
9        </button>
10        <button :disabled="!isRecording" @click="stopCapture">
11          停止录制
12        </button>
13      </div> -->
14
15      <!-- <div v-if="gifUrl" class="output">
16        <h3>录制的GIF</h3>
17        <img :src="gifUrl" alt="GIF 预览" />
18      </div> -->
19    </div>
20  </div>
21</template>
22
23<script setup>
24import request from '/@/utils/request';
25
26import GIF from 'gif.js'
27
28const emit = defineEmits(['sendGif']);
29
30const props = defineProps({
31  fileUrl: {
32    type: String,
33    default: ''
34  }
35})
36
37let theUrl = ref('')
38let timerId = ref(null) // 新增定时器ID
39
40// 监听视频URL变化
41watch(() => props.fileUrl, (newVal) => {
42  theUrl.value = newVal
43  nextTick(() => {
44    if (videoRef.value) {
45      // 清除之前的监听
46      videoRef.value.removeEventListener('loadeddata', handleVideoLoaded)
47      // 添加新的监听
48      videoRef.value.addEventListener('loadeddata', handleVideoLoaded)
49    }
50  })
51})
52
53// 视频加载完成后自动开始录制
54const handleVideoLoaded = () => {
55  console.log('视频加载完成,开始录制')
56
57  // 视频从 20秒 开始播放,即:从20秒开始截取
58  videoRef.value.currentTime = 20;
59
60  startCapture()
61  // 移除监听,避免重复调用
62  videoRef.value.removeEventListener('loadeddata', handleVideoLoaded)
63}
64
65const videoRef = ref(null)
66const isRecording = ref(false)
67const progress = ref(0)
68const gifUrl = ref(null)
69
70let gifRecorder = null
71let captureInterval = null
72
73let gifBlob = ref(null);
74
75// 初始化GIF录制器
76const initGifRecorder = () => {
77  if (gifRecorder) {
78    gifRecorder.abort()
79  }
80  gifRecorder = new GIF({
81    workers: 2,
82    quality: 10,
83    width: videoRef.value?.videoWidth || 320,
84    height: videoRef.value?.videoHeight || 240,
85    workerScript: '/gif.worker.js'
86  })
87
88  gifRecorder.on('progress', p => {
89    progress.value = Math.round(p * 100)
90  })
91
92  gifRecorder.on('finished', blob => {
93    gifUrl.value = URL.createObjectURL(blob)
94    isRecording.value = false
95    progress.value = 0
96    console.log('gifUrl=', gifUrl.value);
97
98    // emit('sendGif', gifUrl.value)
99
100    //#region Blob转Base64
101    // const reader = new FileReader();
102    // reader.onload = () => {
103    //   // const base64Data = reader.result.replace(/^data:image\/\w+;base64,/, '');
104    //   // emit('sendGif', base64Data);
105
106    //   const base64Data = reader.result;
107    //   // console.log('base64Data=', base64Data);
108    //   emit('sendGif', base64Data);
109    // };
110    // reader.onerror = (e) => {
111    //   console.error('Base64转换失败:', e);
112    //   emit('sendGif', null); // 发送失败信号
113    // };
114    // reader.readAsDataURL(blob);
115    //#endregion
116
117    //#region Blob转换为file文件
118    gifBlob.value = blob;
119
120    let file = new File(
121      [gifBlob.value],
122      `recording-${Date.now()}.gif`,
123      { type: 'image/gif' }
124    );
125    console.log('生成的file=', file);
126    uploadGif(file)
127  })
128}
129
130// 捕获帧
131const captureFrame = () => {
132  const canvas = document.createElement('canvas')
133  const ctx = canvas.getContext('2d')
134
135  // 跨域安全验证
136  if (!videoRef.value || !videoRef.value.crossOrigin) {
137    console.error('视频元素未配置跨域访问!')
138    return
139  }
140
141  canvas.width = videoRef.value.videoWidth
142  canvas.height = videoRef.value.videoHeight
143  ctx.drawImage(videoRef.value, 0, 0)
144
145  gifRecorder.addFrame(canvas, {
146    delay: 100,
147    copy: true
148  })
149}
150
151// 开始录制
152const startCapture = () => {
153  if (!videoRef.value || isRecording.value) return
154
155  initGifRecorder()
156  isRecording.value = true
157
158  // 每100ms捕获一帧
159  captureInterval = setInterval(captureFrame, 100)
160
161  // 设置5秒后自动停止录制
162  if (timerId.value) {
163    clearTimeout(timerId.value)
164  }
165  timerId.value = setTimeout(() => {
166    stopCapture()
167    timerId.value = null
168  }, 5000)
169}
170
171// 停止录制
172const stopCapture = () => {
173  if (!isRecording.value) return
174
175  clearInterval(captureInterval)
176  gifRecorder.render()
177  console.log('录制结束,生成GIF')
178
179  // 清除定时器
180  if (timerId.value) {
181    clearTimeout(timerId.value)
182    timerId.value = null
183  }
184}
185
186// 清理资源
187onUnmounted(() => {
188  if (gifRecorder) {
189    gifRecorder.abort()
190  }
191  clearInterval(captureInterval)
192  if (timerId.value) {
193    clearTimeout(timerId.value)
194  }
195})
196
197// 上传接口
198const uploadGif = async (file) => {
199  const { data } = await request({
200    url: '/admin/sys-file/upload',
201    method: 'post',
202    headers: {
203      'Content-Type': 'multipart/form-data',
204      'Enc-Flag': 'false',
205    },
206    data: { file },
207  });
208
209  console.log('data=', data);
210  if (data) {
211    emit('sendGif', data.url);
212  }
213}
214</script>
215
216<style scoped>
217.controls {
218  margin: 1rem 0;
219  display: flex;
220  gap: 1rem;
221}
222
223.progress {
224  margin-top: 1rem;
225}
226
227.output {
228  margin-top: 1rem;
229}
230
231.frame img {
232  width: 100px;
233  height: auto;
234}
235</style>
236
237

父组件使用 demo.vue

1这是随便一个上传组件
2<upload-file
3	@sendFile="getFile"
4	v-model="scope.row.itemPath"
5	:type="'simple'"
6	:fileType="['mp4']"
7/>
8
9<demo :fileUrl="theFileUrl" @sendGif="getGifUrl"></demo>
10
11let theFileUrl = ref('')
12const getFile = (e) => {
13	if(form.itemAddDTOS.length == 1){
14		const file = e.raw;
15		const blob = new Blob([file], { type: file.type });
16		const blobUrl = URL.createObjectURL(blob);
17		theFileUrl.value = blobUrl
18		subBtnFlag.value = true;
19	}
20}
21
22const getGifUrl = (e) => {
23	form.cover = e
24	console.log('e=', e);
25	subBtnFlag.value = false;
26}
27

vue.js 视频截取为 gif - 2(将截取到的gif 转换为base64 、file)》 是转载文章,点击查看原文


相关推荐


华为Java专业级科目一通过心得
想要AC的dly2025/10/22

华为Java专业级科目一通过心得 最近顺利通过了Java专业级科目一考试。在这个过程中,我积累了一些心得体会,希望分享给大家,尤其是那些正在备考的朋友。考试并不像想象中那么难,只要掌握好基础,保持练习,就能轻松应对。下面我从几个方面谈谈我的备考经验。 1. 每天坚持使用Java,保持手感 备考Java考试,最基础的就是要保持对语言的熟悉度。我建议大家每天都用一用Java,哪怕只是写一些简单的代码片段。比如,编写一个算法题、调试一段程序,或者参与开源项目。这样做能帮助你维持“手感”,避免考试


网络速度测试工具——SpeedTest使用指南
好学的Jack2025/10/21

本文还有配套的精品资源,点击获取 简介:SpeedTest是一款用于测量互联网上传下载速度、网络延迟和数据传输稳定性的工具。软件通过连接最近的服务器进行一系列网络测试,帮助用户识别网络问题和性能瓶颈。测试步骤包括初始化测试、下载速度测试、上传速度测试、ping测试、Jitter测试和丢包率测试。安装程序“SpeedTestInstall.exe”允许用户通过简单步骤设置软件,从而进行日常或周期性的网络速度检查。 1. 网络速度测试工具使用 随着互联网技术的发展,家庭和企


EtherCAT转ModbusTCP工业智能网关:开启仓储自动化通信变革的技术桥梁
捷米特研发一部2025/10/20

一、项目背景与核心痛点 某大型智能物流中心占地10万平方米,部署了超过200台自动化设备,其中包括基于Modbus TCP协议的西门子PLC、研华工控机等主站设备,以及采用EtherCAT协议的高精度编码器、智能仪表和协作机器人。由于协议不兼容,设备间通信存在诸多问题: 效率瓶颈:PLC下发分拣指令至EtherCAT机器人响应延迟达500ms,导致分拣线节拍混乱,单日吞吐量下降20%; 故障频发:编码器位置数据无法实时同步至HMI界面,设备异常需人工巡检发现,平均故障停机时间长达1.5小时


Vue3 状态管理完全指南:从响应式 API 到 Pinia
90后晨仔2025/10/19

什么是状态管理? 在 Vue 开发中,状态管理是一个核心概念。简单来说,状态就是驱动应用的数据源。每一个 Vue 组件实例都在管理自己的响应式状态,让我们从一个简单的计数器组件开始理解: <script setup> import { ref } from 'vue' // 状态 - 驱动应用的数据源 const count = ref(0) // 动作 - 修改状态的方法 function increment() { count.value++ } </script> <!-- 视图


Python快速落地的临床知识问答与检索项目(2025年9月教学配置部分)
Allen_Lyb2025/10/17

项目概述与技术选型 本项目定位为临床辅助决策支持工具,而非替代临床诊断的独立系统,旨在解决医疗行业两大核心痛点:一是医学知识更新速率加快,2025 年临床指南年均更新量较 2020 年增长 47%,传统知识管理方式难以同步;二是科室规范呈现碎片化分布,不同院区、亚专科的诊疗流程存在差异,导致知识检索效率低下。技术路线采用 RAG 知识库 + ChatFlow 多轮对话 + 工具节点对接 的三层架构,通过整合指南文献、临床路径和院内 SOP 文档,满足门诊快速问诊、病房随访问答及科室知识库精


滴滴P0级故障背后:互联网公司是如何分级处理线上事故的?
G探险者2025/10/16

大家好,我是G探险者! 像滴滴、阿里、腾讯、华为、字节等大型互联网公司都会对线上故障(事故)进行分级管理,以便快速响应、统一调度、追责复盘。 下面我给你系统性地介绍一下——常见的互联网公司故障分级标准(P0~P4),并结合滴滴、阿里等企业的实践来说明: 🚨 一、故障分级的总体目标 通过分级来 量化故障影响范围与严重程度,从而决定响应等级、通知机制、处理时限与复盘流程。 一般采用的分级体系如下:


面试真实经历某商银行大厂Java问题和答案总结(五)
360_go_php2025/10/15

​ Java 面试常见问题解析​编辑 Java 中的多线程和类加载机制是开发中非常重要的部分。在面试过程中,涉及线程管理和 JVM 的相关问题经常出现。本文将探讨一些 Java 面试中的常见问题,并为每个问题提供详细的解答。 ​编辑--- 1. 线程的同步方法 线程的同步方法是指通过某些机制来控制多线程环境中多个线程对共享资源的访问,以防止数据冲突。以下是几种常见的同步方法: synchronized 关键字: synchronized 关键字可以用来修饰方法或代码块,确保同一时刻只有一个线


Agent 开发设计模式(Agentic Design Patterns )第 2 章:路由模式
shiter2025/10/13

文章大纲 路由模式概览 路由的基本流程 路由机制的类型 路由的部署节点 框架支持 实际应用与用例 1. 人机交互系统 2. 自动化数据处理流水线 3. 多工具/多代理协同系统 4. 系统价值总结 动手代码示例(LangChain) 动手代码示例(Google ADK) 核心总结与最佳实践 什么是


LLM模型与ML算法之间的关系
Juchecar2025/10/12

三要素 机器学习的是算法、算力和数据,LLM的是模型、算力和数据。 ——约去同类项,那么问题来了:算法和模型之间是什么关系? 这是一个非常敏锐且深刻的观察。您提出的论述没有根本性的逻辑矛盾,但揭示了在不同技术范式下,对核心要素的表述侧重点发生了转移。 我们可以从两个层面来理解这个问题: “算法”和“模型”在概念上是相通的,但存在层次和侧重点的差异。 LLM是机器学习的一个子集(尽管是极其庞大和重要的一个),其要素的表述反映了其技术范式的特殊性。 下面我们来详细拆解。 1. “算法”与“模型”


【腾讯拥抱开源】Youtu-Embedding:基于CoDiEmb的一个协作而独特的框架,用于信息检索与语义文本相似性中的统一表征学习
吴脑的键客2025/10/10

🎯 简介 Youtu-Embedding 是由腾讯优图实验室开发的尖端通用文本嵌入模型。该模型在信息检索(IR)、语义文本相似度(STS)、聚类、重排序和分类等各类自然语言处理任务中均展现出卓越性能。 顶尖性能表现:截至2025年9月,在权威的CMTEB(中文大规模文本嵌入基准)评测中以77.46分位列榜首,彰显其强大稳健的文本表征能力。 创新训练框架:采用协同判别式微调框架,通过统一数据格式、任务差异化损失函数及动态单任务采样机制,有效解决多任务学习中的"负迁移"问题。 注:您可

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0