JavaScript实现上传前端包图片(基于Vue3)
背景
起因是后台管理系统要实现一个功能:
- 前端包可配置默认图片5张供用户选择(默认第一张),用户也可以上传自己本地的图片
- 当用户选中某张图片并做表单保存时,需要前端用JS实现上传前端包的图片资源
需求分析
-
选中某张图片之后,可以得到该图片的
src
,就是要通过图片url
生成一个image/jpeg
类型文件并上传 -
大致流程:
new Image()
生成一个图片对象image
,image.src
赋为该图片的src
- 给
image
添加onload
事件(在图片加载完成后立即执行),当触发onload
事件时 - 通过
Canvas
对象将image
绘制出来 - 再通过
Canvas
对象的toBlob(callback, fileType)
方法将Canvas
对象转为blob
文件流,就可以上传了
具体实现
由于想学一学 Vue3,我这次用的技术栈是:pnpm + Vite + Vue3 + Vue Router 4 + JavaScript + Axios + SCSS,搭建了一个简易版项目 github地址,一路举步维艰磕磕绊绊可算是搞出了一个丐版上传图片
用 Vite 搭建 Vue3 项目
1. 安装 pnpm
# 全局安装 pnpm
npm install pnpm -g
# 查看源
pnpm config get registry
# 切换淘宝源
pnpm config set registry http://registry.npm.taobao.org
2. 创建 Vite + Vue3 项目
# 快速创建
pnpm create vite vue3-test-app --template vue
# 常规创建
pnpm create vite
# 输入项目名称:vue3-test-app
# 选择框架:Vue
# 选择语言:JavaScript
3. 安装各种需要的依赖
# 安装 scss
pnpm add sass -D
# 安装 axios
pnpm add axios
# 安装 vue-router 4
pnpm add vue-router@4
4. 配置 vite.config.js
-
引入全局
scss
样式文件- 配置项
css.preprocessorOptions
用来指定传递给 CSS 预处理器的选项 - 这样就可以在全局中使用
variables.scss
文件中预定义的变量了
- 配置项
-
配置
server.host
为0.0.0.0
(监听所有地址,包括局域网和公网地址):这样可以在网络中暴露服务-
解决
vite
启动后出现 Network: use `--host` to expose -
同时也可以让局域网内的电脑和手机访问到网页
-
-
配置
server.open
为true
:可以在开发环境启动项目时自动在浏览器中打开应用程序 -
配置
server.hmr
为true
:配置 HMR 连接,实现热更新-
所谓热更新就是,开发过程中修改程序之后页面也能及时更新
-
如果不配置热更新,就只能重启程序才能实现页面的更新
-
如果配置了页面还是不更新,需要检查一下路由文件引入的页面名称大小写是不是正确(查了好久原因才发现这个大坑啊╰(艹皿艹 ) )
-
-
配置
server.proxy
代理
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
// 引入scss
scss: {
additionalData: '@import "./src/styles/variables.scss";'
}
}
},
server: {
// 监听所有地址,包括局域网和公网地址
host: '0.0.0.0',
port: '8080',
// 开发环境启动项目后自动在浏览器中打开一个网页
open: true,
cors: true,
// 配置开启热更新 - 解决 修改了 .vue 文件保存后,视图没有更新,需要重新启动项目才行
// 如果配置了还不生效,需要检查路由中文件名大小写是否正确
hmr: true,
// 配置代理
proxy: {
'/appointmentapi': {
target: 'http://192.168.9.201:14084',
changeOrigin: true
}
}
}
})
5. 生成 vue-router 实例
// router.js
// 引入 vue-router
import * as VueRouter from 'vue-router'
const base = [
{ path: '', redirect: { name: 'home' } },
{
path: '/home',
name: 'home',
component: () => import('./views/home/HomeIndex.vue'),
meta: { title: '首页' }
}
]
const upload = [
{
path: '/upload/auto',
name: 'upload_auto',
component: () => import('./views/upload/UploadAuto.vue'),
meta: { title: '上传' }
}
]
// routes配置项
const routes = base.concat(upload)
// 创建 router 实例
const router = VueRouter.createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,在这里使用 hash 模式
history: VueRouter.createWebHashHistory(),
routes
})
// 导出 router 实例
export default router
// main.js
import { createApp } from 'vue'
import './styles/index.scss'
import App from './App.vue'
import router from './router.js'
// 引入并使用 router
createApp(App).use(router).mount('#app')
上传图片页
1. 最终效果
- 直接上传本地图片,使用
<input type="file">
的原生文件上传
- JS模拟上传
2. 图片 url
转 blob
文件流
- 图片url → Image对象img
const img = new Image()
: 会创建一个HTMLImageElement
实例img
,等同于const img = document.createElement('img')
。我们会用到img
的一些属性和事件:img.src
: 设置或返回图像的 URLimg.width
,img.height
:设置或返回图像的宽高img.onload
:设置图像加载完毕后的操作
img.onload = () => { // 后续转blob文件流及上传的操作 }
: 先设置onload
事件一方面是设置了img.src
后,会马上进行异步图片加载,如果在达到onload之前加载,则onload将不会触发;另一方面也是兼容某些低版本浏览器(IE)onload无效(onload写到src前面,先告诉浏览器图片加载完要怎么处理,再让它去加载图片=>不是IE浏览器不会触发onload事件,而是因为加载缓冲区的速度太快,在没有告诉它加载完要怎么办时,它已经加载完了)img.src = '图片url'
: 定义Image
对象的src
,相当于给浏览器缓存了一张图片
在网上查找
onload
兼容浏览器相关资料时看到一篇还不错的文章:图片onload事件详解,兼容所有浏览器! - 众里寻它 - 博客园 (cnblogs.com)
-
Image对象 → Canvas对象
const canvas = document.createElement('canvas')
: 创建一个Canvas元素,它公开了一个或多个渲染上下文,可以用来绘制和处理要展示的内容const ctx = canvas.getContext('2d')
: 用来获得渲染上下文(2D)和它的绘画功能canvas.width=img.width
,canvas.height=img.height
: 设置画布宽高为图片的宽高ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
: 用前面获取到的2D渲染上下文来绘制图片
-
Canvas对象 → Blob对象
canvas.toBlob(callback(file), mimeType, quality)
: 通过Canvas对象直接生成blob
文件流,这个blob图片文件可以被缓存或保存到本地(由用户代理自行决定)callback回调
会入参生成的blob
文件流mimeType
代表图像的 MIMEType 类型(字符串),默认是image/png
quality
代表图片质量,0到1之间,=只对image/jpeg
和image/webp
类型有效
3. blob
文件流上传
前端发起请求可以有很多种方式,这里我们用的是 Axios
(关于Axios
的内容可以参考官方文档);而上传给后端接口的文件数据需要以FormData
格式存储
const data = new FormData()
// 参数名,参数值,文件名称
data.append('file', file, 'image1.png')
- 由于我们前面已经得到的是blob文件流,可以直接向 FormData 对象附加 File 或 Blob 类型的文件(如下)
- 如果不指定文件名称(第三个参数),文件名称就会是'blob',这需要根据后端具体接口来决定是否必须传文件名称
第三个参数是:传给服务器的文件名称 (一个
USVString
),当一个Blob
或File
被作为第二个参数的时候,Blob
对象的默认文件名是 "blob"。File
对象的默认文件名是该文件的名称
4. 核心代码
详细代码请转至GitHub,后面也可能会继续完善代码,这里就只展示出JS部分代码
<script setup>
import { ref, reactive } from 'vue'
import axios from 'axios'
// 图片列表
const bannerList = [
{ src: 'config/upload/banner1.jpg', name: 'banner1.jpg' },
{ src: 'config/upload/banner2.jpg', name: 'banner2.jpg' },
{ src: 'config/upload/banner3.jpg', name: 'banner3.jpg' },
{ src: 'config/upload/banner4.jpg', name: 'banner4.jpg' },
{ src: 'config/upload/banner5.jpg', name: 'banner5.jpg' },
]
// 选中的图片
let active = ref(0)
// 最终上传的图片文件
let attach = ref(null)
/** 选择一张图片 */
const select = (index) => {
active.value = index
}
/** 上传选中图片 */
const uploadSelect = () => {
if (active.value < 0) {
return
}
// 生成图片
const item = bannerList[active.value] // 选中的选项
const image = new Image()
image.onload = () => { // 图片加载完毕后
// 图片绘制到Canvas对象
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = image.width
canvas.height = image.height
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
// Canvas对象转blob文件流
canvas.toBlob((file) => {
upload(file, item.name)
}, 'image/jpeg')
}
image.src = item.src
}
/** 普通上传图片 */
const uploadImg = (e) => {
const files = e.target.files
if (!files.length) { // 没有选择文件
return
}
active.value = -1
upload(files[0])
}
/** 接口上传 */
const upload = (file, fileName = undefined) => {
// 创建axios实例
const service = axios.create({
baseURL: 'http://192.168.9.201:14084/appointmentapi',
timeout: 30000
})
// 请求拦截
service.interceptors.request.use(config => {
config.headers.Authorization = '一个token'
return config
}, error => {
return Promise.reject(error)
})
const data = new FormData()
data.append('file', file, fileName)
service.post('/common/upload', data, {
headers: { 'Content-Type': 'multipart/form-data' }
}).then(response => {
console.log(response)
const res = response.data
if (res.success) { // 成功
attach.value = res.data
}
})
}
</script>
转载自:https://juejin.cn/post/7170984142883667982