获取 File 对象的几种途径
1、input[type=file]
通过表单元素 file input 拿取 File 对象。
MDN 参考:
<input type="file" id="file" />
const input = document.querySelector('#file')
const files = Array.from(input.files)
files.forEach((file) => {
// 执行 File 处理程序
})
2、drop event
监听鼠标拖拽,获取 File 对象
MDN 参考:
<div id="file"></div>
const file = document.querySelector('#text')
file.addEventListener('drop', function (e) {
e.preventDefault()
// 千万不要加 e.stopPropagation()
if (e.dataTransfer && e.dataTransfer.files) {
const files = Array.from(e.dataTransfer.files)
files.forEach((file) => {
// 执行 File 处理程序
})
}
})
3、paste event
监听粘贴事件,从粘贴板中获取 File 对象
MDN 参考:
document.addEventListener('paste', function (e) {
const files = Array.from(e.clipboardData.files)
files.forEach(file => {
// 执行 File 处理程序
})
})
4、xhr(Blob To File)
从服务器上加载为 Blob 资源,并将 Blob 转换为 File 对象。
原理:File 是特殊类型的 Blob。
MDN 参考:
封装 xhr 功能函数
/**
* 加载远程资源为 File
* @param {String} url 服务器资源地址
* @param {String} name 文件名
* @return {Promise<File>}
*/
function loadForFile(url, name) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'blob'
xhr.addEventListener('load', function() {
const file = new File([xhr.response], name, {
type: xhr.response.type,
lastModified: new Date(),
})
resolve(file)
})
xhr.addEventListener('error', reject)
xhr.send()
})
}
xhr 功能函数的使用
loadForFile('http://xxx/tt.mp3', 'tt.mp3').then((file) => {
// 执行 File 处理程序
})
canvas(Blob To File)
从 canvas 上获取 File 有两种方式,一种是已经被弃用的 HTMLCanvasElement.mozGetAsFile(知道即可);另一种则是同 xhr 一样的原理,通过 Blob 转 File。
MDN 参考:
简单的示例,更多示例请移步图片压缩
canvas.toBlob(function call(blob) {
const file = new File([blob], 'image.png', { type: blob.type })
// 执行 File 处理程序
}, 'image/png')
File 资源预览
MDN 参考:
// 创建 URL 对象
const url = URL.createObjectURL(file)
// 预览图片
document.body.insertAdjacentHTML('beforeend', `<img src="${url}" />`)
// 预览音频
document.body.insertAdjacentHTML('beforeend', `<audio controls src="${url}"></audio>`)
// 预览视频
document.body.insertAdjacentHTML('beforeend', `<video controls src="${url}"></video>`)
// 预览 PDF
document.body.insertAdjacentHTML('beforeend', `<iframe src="${url}"></iframe>`)
// 释放 URL 对象(建议异步释放资源,否则 img/audio/video/iframe 标签可能会资源加载失败,影响预览效果)
setTimeout(() => {
URL.revokeObjectURL(url)
}, 5000)
读取 File 资源
MDN 参考:
封装资源读取功能函数
/**
* @param {File|Blob} file 二进制对象(File类型 或 Blob类型)
* @param {String} type 以怎样的方式读取
* @param {String} encoding 传入一个字符串类型的编码类型,如缺省,则默认为“utf-8”类型
* @return {Promise<String | ArrayBuffer>}
*/
function read(file, type, encoding = 'utf-8') {
return new Promise((resolve, reject) => {
const reader = new FileReader()
// 返回错误信息
reader.addEventListener('error', function () {
reject(new Error('FileReader 读取资源失败'))
})
// 读取成功,返回读取到的资源
reader.addEventListener('load', function () {
resolve(reader.result)
})
// 不同的读取方式
switch(type) {
case 'text':
reader.readAsText(file, encoding)
break
case 'base64':
reader.readAsDataURL(file)
break
// case 'buffer':
default:
reader.readAsArrayBuffer(file)
break
}
})
}
案例:获取文本内容(FileReader.readAsText)
读取文本内容示例:HTML 部分
<input type="file" accept="text/plain" id="file" />
<textarea rows="3" cols="50" id="tarea"></textarea>
读取文本内容示例:JavaScript 部分
const input = document.querySelector('#file')
const tarea = document.querySelector('#tarea')
input.addEventListener('change', function() {
const file = input.files[0]
if (file) {
read(file, 'text').then(text => {
tarea.textContent = text
}).catch(({ message }) => {
alert(message)
})
}
})
案例:图片压缩(FileReader.readAsDataURL)
MDN 参考:
- HTMLCanvasElement
- HTMLCanvasElement.toDataURL
- HTMLCanvasElement.toBlob
- CanvasRenderingContext2D
- CanvasRenderingContext2D.drawImage
封装图片压缩功能函数
/**
* 图片压缩
* @param {File|Blob} file 二进制对象(File类型 或 Blob类型)
* @param {Number} ratio 图片压缩的比例,介于 0 ~ 1 之间的数字
* @param {Number} encoderOptions 当图片格式为 image/jpeg 或者 image/webp 时用来指定图片展示质量,介于 0 ~ 1 之间的数字
* @return {Promise<{ file: File, base64: String }>}
*/
function compress(file, ratio = 0.75, encoderOptions = 0.92) {
return read(file, 'base64').then(base64 => {
return new Promise((resolve, reject) => {
const image = new Image()
image.addEventListener('error', function () {
reject(new Error('compress 图片资源加载失败'))
})
image.addEventListener('load', function () {
// 验证压缩比例是否合法(0~1 之间的数)
ratio = ratio > 1 ? 1 : ratio < 0 ? 0 : ratio
// 计算压缩后图片的宽高
const width = image.naturalWidth * ratio
const height = image.naturalHeight * ratio
// 初始化 canvas
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
// 绘制图片
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, width, height)
ctx.drawImage(image, 0, 0, width, height)
// canvas.toBlob 的回到函数
function call(blob) {
resolve({
file: new File([blob], file.name, { type: file.type }), // 将 file.type 作为自定义参数可以实现图片的格式转换
base64: canvas.toDataURL(file.type, encoderOptions), // 返回 base64 的图片资源便于效果预览
})
}
canvas.toBlob(call, file.type, encoderOptions)
})
image.src = base64
})
})
}
图片压缩案例:HTML 部分
<input type="file" accept="image/*" id="file" />
<img src="" id="view1" alt="原图预览">
<img src="" id="view2" alt="压缩图预览">
图片压缩案例:JavaScript 部分
const input = document.querySelector('#file')
const img1 = document.querySelector('#view1')
const img2 = document.querySelector('#view2')
input.addEventListener('change', function () {
if (input.files[0]) {
const file = input.files[0]
// 原图预览
img1.src = URL.createObjectURL(file)
// 对图片的压缩比为 0.5
compress(file, 0.5).then((data) => {
// 压缩图预览
img2.src = data.base64
console.log(data)
}).catch(({ message }) => {
console.log(message)
})
}
})
案例:音频截取(FileReader.readAsArrayBuffer)
MDN 参考:
- Blob
- DataView
- Float32Array
- ArrayBuffer
- AudioBuffer
- AudioContext.decodeAudioData 依赖库:
- audiobuffer-to-wav
封装音频截取功能函数
/**
* 音频截取
* @param {File|Blob} file 二进制对象(File类型 或 Blob类型)
* @param {Number} start 开始截取的时间(秒)
* @param {Number} length 截取的时间长度(秒)
* @return {Promise<AudioBuffer>}
*/
function cutout(file, start, length) {
return read(file, 'buffer').then((buffer) => {
return new Promise((resolve, reject) => {
const ctx = new AudioContext()
// 当 ArrayBuffer 转 AudioBuffer 成功解码后会被调用的回调函数
function success(audioBuffer) {
const channels = audioBuffer.numberOfChannels
const rate = audioBuffer.sampleRate
start *= rate
// 帧数
const frame = rate * length
// 创建采用率、同样声道数量,长度均相等的空 AudioBuffer
const temp = ctx.createBuffer(channels, frame, rate)
// 创建临时的Array存放复制的buffer数据
const f32a = new Float32Array(frame)
// 声道的数据的复制和写入
for (let channel = 0; channel < channels; channel++) {
audioBuffer.copyFromChannel(f32a, channel, start)
temp.copyToChannel(f32a, channel, 0)
}
resolve(temp)
}
// 当 ArrayBuffer 转 AudioBuffer 解码失败后的的回调函数
function error() {
reject(new Error('ArrayBuffer 转 AudioBuffer 失败'))
}
// ArrayBuffer 转 AudioBuffer
ctx.decodeAudioData(buffer, success, error)
})
})
}
/**
* 将 AudioBuffer 转为 Blob
* @param {AudioBuffer} buffer 音频资源
* @return {Blob}
*/
function bufferToBlob(buffer) {
// toWavm 为外部依赖库:https://www.npmjs.com/package/audiobuffer-to-wav
return new Blob([new DataView(toWavm(buffer))], {
type: 'audio/wav'
})
}
音频截取示例:HTML 部分
<input type="file" name="file" id="file" />
<audio src="" controls id="view1"></audio>
<audio src="" controls id="view2"></audio>
音频截取示例:JavaScript 部分
const input = document.querySelector('#file')
const audio1 = document.querySelector('#view1')
const audio2 = document.querySelector('#view2')
input.addEventListener('change', function () {
if (input.files[0]) {
const file = input.files[0]
// 原音频预览
audio1.src = URL.createObjectURL(file)
// 从第 10 秒开始截取 15 秒长度的音频
cutout(file, 10, 15).then((buffer) => {
const blob = bufferToBlob(buffer)
// 截取的音频预览
audio2.src = URL.createObjectURL(blob)
console.log({ buffer, blob })
})
}
})