nextboy / skill Goto Github PK
View Code? Open in Web Editor NEWweb实战中的一些小套路
web实战中的一些小套路
vue-quill-editor是我们再使用vue框架的时候常用的一个富文本编辑器,在进行富文本编辑的时候,我们往往要插入一些图片,vue-quill-editor默认的处理方式是直接将图片转成base64编码,这样的结果是整个富文本的html片段十分冗余,通常来讲,每个服务器端接收的post的数据大小都是有限制的,这样的话有可能导致提交失败,或者是用户体验很差,数据要传递很久才全部传送到服务器。
因此,在富文本编辑的过程中,对于图片的处理,我们更合理的做法是将图片上传到服务器,再将图片链接插入到富文本中,以达到最优的体验。
废话不多说,接下来直接看如何改造
查阅网上的资料,我感觉提供的方案都不是特别友好,网上搜索的基本都是这一个方法
配合 element-ui 实现上传图片/视频到七牛或者是直接重新写一个按钮来进行自定义图片操作
坦白讲,上面这2个方法都很特别,也的确有效果,但是我个人还是觉得不完美,第一个方法写得太麻烦,第二个方法有点投机取巧。
结合上面两种方法以及官方的文档,我这里提供一个新的改造思路给大家参考。
和第一种方法类似,为了更好的控制上传的图片,我这里也是引用了element-ui的上传图片组件
<template>
<div>
<!-- 图片上传组件辅助-->
<el-upload
class="avatar-uploader"
:action="serverUrl"
name="img"
:headers="header"
:show-file-list="false"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload">
</el-upload>
</div>
</template>
<script>
export default {
data() {
return {
serverUrl: '', // 这里写你要上传的图片服务器地址
header: {token: sessionStorage.token} // 有的图片服务器要求请求头需要有token
}
},
methods: {
// 上传图片前
beforeUpload(res, file) {},
// 上传图片成功
uploadSuccess(res, file) {},
// 上传图片失败
uploadError(res, file) {}
}
}
</script>
这里要使用element-ui主要有2个好处
:on-success="uploadSuccess" // 图片上传成功
:on-error="uploadError" // 图片上传失败
:before-upload="beforeUpload" // 图片上传前
这里对于如何安装和引入vue-quill-editor和就不多做陈述了,不清楚的同学自己Google下哈。
在代码中写入vue-quill-editor后如下
<template>
<div>
<!-- 图片上传组件辅助-->
<el-upload
class="avatar-uploader"
:action="serverUrl"
name="img"
:headers="header"
:show-file-list="false"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload">
</el-upload>
<!--富文本编辑器组件-->
<el-row v-loading="uillUpdateImg">
<quill-editor
v-model="detailContent"
ref="myQuillEditor"
:options="editorOption"
@change="onEditorChange($event)"
@ready="onEditorReady($event)"
>
</quill-editor>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
serverUrl: '', // 这里写你要上传的图片服务器地址
header: {token: sessionStorage.token}, // 有的图片服务器要求请求头需要有token之类的参数,写在这里
detailContent: '', // 富文本内容
editorOption: {} // 富文本编辑器配置
}
},
methods: {
// 上传图片前
beforeUpload(res, file) {},
// 上传图片成功
uploadSuccess(res, file) {},
// 上传图片失败
uploadError(res, file) {}
}
}
</script>
这里可以看到我们用一个包裹我们的富文本组件,是为了使用loading动画,就是v-loading这个设置
从下图可以看到,默认的配置中,整个工具栏具备了所有的功能,自然也包括红圈中的图片上传功能了。
那么接下来我们要怎么去重写这个按钮的事件呢。
很简单,我们需要在editorOption配置中这么写
export default {
data() {
return {
serverUrl: '', // 这里写你要上传的图片服务器地址
header: {token: sessionStorage.token}, // 有的图片服务器要求请求头需要有token之类的参数,写在这里
detailContent: '', // 富文本内容
editorOption: {
placeholder: '',
theme: 'snow', // or 'bubble'
modules: {
toolbar: {
container: toolbarOptions, // 工具栏
handlers: {
'image': function (value) {
if (value) {
document.querySelector('#quill-upload input').click()
} else {
this.quill.format('image', false);
}
}
}
}
}
}
}
}
}
配置中的handlers是用来定义自定义程序的,然而我们配置完后会懵逼地发现,整个富文本编辑器的工具栏的图片上传等按钮都不见了 只保留了几个基本的富文本功能。
这个是因为添加自定义处理程序将覆盖默认的工具栏和主题行为
因此我们要再自行配置下我们需要的工具栏,所有功能的配置如下,大家可以按需配置
<script>
// 工具栏配置
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{'header': 1}, {'header': 2}], // custom button values
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
[{'direction': 'rtl'}], // text direction
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}], // dropdown with defaults from theme
[{'font': []}],
[{'align': []}],
['link', 'image', 'video'],
['clean'] // remove formatting button
]
export default {
data() {
return {
editorOption: {
placeholder: '',
theme: 'snow', // or 'bubble'
modules: {
toolbar: {
container: toolbarOptions, // 工具栏
handlers: {
'image': function (value) {
if (value) {
alert(1)
} else {
this.quill.format('image', false);
}
}
}
}
}
}
}
}
}
</script>
由于这里的工具栏配置列举了所有,看起来很长一堆,我建议大家可以写在单独一个文件,然后再引入,美观一点
经过上面的配置,大家点击一下图片,可以看出弹出了个1,说明我们的自定义事件生效了,那么接下来,大家的思路是不是就很清晰啦?
我们需要在handles里面继续完善我们的图片点击事件。
handlers: {
'image': function (value) {
if (value) {
// 触发input框选择图片文件
document.querySelector('.avatar-uploader input').click()
} else {
this.quill.format('image', false);
}
}
}
在这里我们的自定义事件就结束了,接下来图片上传成功或者失败都由
:on-success="uploadSuccess" // 图片上传成功
:on-error="uploadError" // 图片上传失败
:before-upload="beforeUpload" // 图片上传前
这三个函数来处理
// 富文本图片上传前
beforeUpload() {
// 显示loading动画
this.quillUpdateImg = true
},
uploadSuccess(res, file) {
// res为图片服务器返回的数据
// 获取富文本组件实例
let quill = this.$refs.myQuillEditor.quill
// 如果上传成功
if (res.code === '200' && res.info !== null) {
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片 res.info为服务器返回的图片地址
quill.insertEmbed(length, 'image', res.info)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
this.$message.error('图片插入失败')
}
// loading动画消失
this.quillUpdateImg = false
},
// 富文本图片上传失败
uploadError() {
// loading动画消失
this.quillUpdateImg = false
this.$message.error('图片插入失败')
}
好了,本文就讲到这,目前运行良好,整个文章的代码比较多,但是实际上需要去深入理解的地方很少,我们只是简单重定义了图片按钮的触发事件。
对了,大家别忘记安装element-ui和vue-quill-editor哦。
如果有错误,欢迎大家多提提意见,希望这篇文章能帮到有需要的人。
不是每个网页端的用户都能用得起光纤,不是每张图片都是压缩得很小,有时候我们也想要看高清大图,但是受限于网速有时候场景是这样的:(很明显左边的第一张图片还没出来,其他的就出来了)
图片资源预加载是一个很常见的需求,在网页开发中,
在浏览器向服务器发送请求的过程中,如果图片资源已经加载过一次了,则不会再从服务器加载同一个图片,
利用这个原理,我们的实现思路如下:
// 创建一个图片对象
let img = new Image()
img.src = '图片地址'
// 资源图片加载完毕
img.onload = function() {}
基于这个原理我封装了一个图片资源加载的函数,其结构如下
/**
* @description 图片资源加载函数
* 适用于canvas加载图片,返回的是一个promise异步对象,response 的值为一个资源对象
* @param {Object} config 参数设置,是一个对象
* config.sourceData - 资源对象 {key: value} 资源名:资源地址
* config.mode - 默认为false, 即使有失败也会返回, mode为true,开启严格模式,一个失败则全部不返回
* config.target - 要预加载的目标节点对象
* config.response - 默认false, 是否返回promise异步对象,true为返回
* @return {null || Promise}
*/
const loadImg = (config) => {
// 初始化设置参数
let sourceData = config.sourceData
let mode = config.mode || false
let target = config.target || []
let needRes = config.response || false
// 创建promise对象
let promise = new Promise((resolve, reject) => {
// 函数内部
// 完整代码在最底层
})
}
下面让我们先来看看这个函数的威力吧!
函数loadImg()用于图片加载(函数实现代码在最下面),接受的参数为一个设置对象config, 该对象有4个参数键值
基本调用方式如下
// 图片资源对象, 对象的key值是自定义的
// 到时候如果有返回,返回的response对象的key值跟这个一样
let data = {
img1: 'http://plaechold.it/200x200',
img2: 'http://plaechold.it/200x200',
img3: 'http://plaechold.it/200x200'
}
// 需要预加载的节点集合
let images = document.querySelectorAll('img')
// 开启资源加载
loadImg({
sourceData: data, // 图片资源对象
target: images, // 预加载目标
mode: true, // 是否开启严格模式
response: false // 是否返回异步对象
})
let canvas = document.querySelector('canvas')
let context = canvas.getContext('2d')
// 图片资源对象
let data = {
bird: 'http://plaechold.it/200x200', // 一只鸟的图片
person: 'http://plaechold.it/200x200', // 一个人类的图片
tiger: 'http://plaechold.it/200x200' // 一只老虎的图片
}
// 资源加载
loadImg({
sourceData: data, // 图片资源对象
mode: true, // 是否开启严格模式
response: true // 返回异步对象,然后可以使用then获取结果进行下一步处理
})
.then(res => {
// res 也是一个对象{ bird: 图片对象, person: 图片对象, tiger: 图片对象 }
// 获取所有的图片对象
let images = Object.values(res)
images.forEach((value, index) => {
context.drawImage(value, index * 100, index * 100)
})
})
// 记住必须设置config.response 的值为 true 才能用then进行数据操作
<!--在html中图片的src地址请务必为空,src="" -->
<img alt="1" src="">
<img alt="2" src="">
<img alt="3" src="">
// 图片资源对象
let data = {
bird: 'http://plaechold.it/200x200', // 一只鸟的图片
person: 'http://plaechold.it/200x200', // 一个人类的图片
tiger: 'http://plaechold.it/200x200' // 一只老虎的图片
}
let images = document.querySelectorAll('img')
// 资源加载
loadImg({
sourceData: data, // 图片资源对象
target: images, // 要渲染的节点集
mode: true, // 是否开启严格模式
})
在vue中,由于vue是双向数据绑定,因此我们可以先这样:
假设我要加载的图片html代码如下
<img :src = "imgSrc.img1">
<img :src = "imgSrc.img2">
<img :src = "imgSrc.img3">
也就是说图片的地址是动态的,绑定着imgSrc
整个实现代码如下:
<template>
<div class="img-wrap">
<img :src = "imgSrc.img1">
<img :src = "imgSrc.img2">
<img :src = "imgSrc.img3">
</div>
</template>
<script >
export default {
data() {
// 图片绑定的数据
imgSrc: {
img1: '',
img2: '',
img3: ''
},
// 要加载的图片地址
loadSrc: {
img1: 'http://placehold.it/100x100',
img2: 'http://placehold.it/200x200',
img3: 'http://placehold.it/300x300'
}
},
created() {
loadImg({
sourceData: this.loadSrc,
response: true // 开启返回response
})
.then(() => {
// 将加载好的图片地址赋值给imgSrc
this.imgSrc = Object.assign({}, this.loadSrc)
})
}
}
</script>
基于这个函数的功能,我们可以做很多事情,除了上面的canvas绘图和展示图片外,还可以利用该
函数进行预加载,譬如当用户在浏览第一页的时候,我们可以先加载第二页的图片,具体的实现例子
这里就不多做展示了。简单的如下
let nextImages = document.querySelectorAll('.next-img') // 下一页的图片节点
// 用户只移动到按钮上面还没点击
button.hover = function() {
// 预加载图片资源
loadImg({
sourceData: data, // 图片资源对象
target: nextImages, // 要渲染的节点集
mode: true, // 是否开启严格模式
})
}
// 这样当用户click点击以后图片资源已经在hover的时候就加载了
完整的函数实现代码如下
/**
* @description 资源图片加载函数
* 适用于canvas加载图片,返回的是一个promise异步对象,response 的值为一个资源对象
* @param {Object} config 参数设置,是一个对象
* config.sourceData - 资源对象 {key: value} 资源名:资源地址
* config.mode - 默认为false, 即使有失败也会返回, mode为true,开启严格模式,一个失败则全部不返回
* config.target - 要预加载的目标节点对象
* config.response - 默认false, 是否返回promise异步对象,true为返回
* @return {null || Promise}
*/
const loadImg = (config) => {
// 初始化设置参数
let sourceData = config.sourceData
let mode = config.mode || false
let target = config.target || []
let needRes = config.response || false
// 创建promise对象
let promise = new Promise((resolve, reject) => {
// 资源加载进度
let loadNum = 0
// 资源加载的结果集
let response = {}
// 如果是非严格模式
if (mode === false) {
// 遍历加载每个资源
Object.keys(sourceData).forEach(key => {
let source = new Image()
// 失败或者成功都写入response
source.onload = source.onerror = () => {
response[key] = source
loadNum++
if (loadNum === Object.keys(sourceData).length) {
// 如果有目标对象
if (target) {
target.forEach((item, index) => {
item.src = Object.values(sourceData)[index]
})
}
// 成功
resolve(response)
}
}
// src的赋值放在onload和onerror事件后面,这样才能兼容IE
source.src = sourceData[key]
})
} else if (mode === true) { // 严格模式:一个失败直接结束且返回空对象
// 遍历加载每个资源
Object.keys(sourceData).forEach(key => {
let source = new Image()
// 成功则写入response
source.onload = () => {
response[key] = source
loadNum++
if (loadNum === Object.keys(sourceData).length) {
// 如果有目标对象
if (target) {
target.forEach((item, index) => {
item.src = Object.values(sourceData)[index]
})
}
resolve(response)
}
}
// 失败则返回空
source.onerror = () => {
// 失败
reject({})
}
// src的赋值必须放在最后,兼容IE
source.src = sourceData[key]
})
}
})
// 结束
if (needRes) {
return promise
}
}
// 图片上传参数配置
const uploadConfig = {
action: '', // 必填参数 图片上传地址
methods: 'POST', // 必填参数 图片上传方式
token: sessionStorage.token, // 可选参数 如果需要token验证,假设你的token有存放在sessionStorage
name: 'img', // 必填参数 文件的参数名
size: 500, // 可选参数 图片大小,单位为Kb, 1M = 1024Kb
accept: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' // 可选 可上传的图片格式
}
// toolbar工具栏的工具选项(默认展示全部)
const toolOptions = [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{'header': 1}, {'header': 2}],
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'script': 'sub'}, {'script': 'super'}],
[{'indent': '-1'}, {'indent': '+1'}],
[{'direction': 'rtl'}],
[{'size': ['small', false, 'large', 'huge']}],
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}],
[{'font': []}],
[{'align': []}],
['clean'],
['link', 'image', 'video']
]
// handler重写事件, 任何工具按钮的功能都可以重写,这里只重写图片上传事件
const handlers = {
image: function image() {
var self = this;
var fileInput = this.container.querySelector('input.ql-image[type=file]');
if (fileInput === null) {
fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
// 设置图片参数名
if (uploadConfig.name) {
fileInput.setAttribute('name', uploadConfig.name);
}
// 可设置上传图片的格式
fileInput.setAttribute('accept', uploadConfig.accept);
fileInput.classList.add('ql-image');
// 监听选择文件
fileInput.addEventListener('change', function () {
// 如果图片限制大小
if (uploadConfig.size && fileInput.files[0].size >= uploadConfig.size * 1024) {
fileInput.value = ''
return
}
// 创建formData
var formData = new FormData()
formData.append(uploadConfig.name, fileInput.files[0])
// 如果需要token且存在token
if (uploadConfig.token) {
formData.append('token', uploadConfig.token)
}
// 图片上传
var xhr = new XMLHttpRequest()
xhr.open(uploadConfig.methods, uploadConfig.action, true)
// 上传数据成功,会触发
xhr.onload = function (e) {
if (xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
// console.log(res)
let length = self.quill.getSelection(true).index;
self.quill.insertEmbed(length, 'image', res.info)
self.quill.setSelection(length + 1)
}
fileInput.value = ''
}
// 开始上传数据
xhr.upload.onloadstart = function (e) {
fileInput.value = ''
// console.log('开始上传')
}
// 当发生网络异常的时候会触发,如果上传数据的过程还未结束
xhr.upload.onerror = function (e) {
}
// 上传数据完成(成功或者失败)时会触发
xhr.upload.onloadend = function (e) {
// console.log('上传结束')
}
xhr.send(formData)
});
this.container.appendChild(fileInput);
}
fileInput.click();
}
}
// 全部配置
export const editorOptions = {
placeholder: '',
theme: 'snow', // 主题
modules: {
toolbar: {
container: toolOptions, // 工具栏选项
handlers: handlers // 事件重写
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.