Giter Club home page Giter Club logo

skill's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

skill's Issues

改造vue-quill-editor:实现图片上传到服务器再插入富文本

需求概述

vue-quill-editor是我们再使用vue框架的时候常用的一个富文本编辑器,在进行富文本编辑的时候,我们往往要插入一些图片,vue-quill-editor默认的处理方式是直接将图片转成base64编码,这样的结果是整个富文本的html片段十分冗余,通常来讲,每个服务器端接收的post的数据大小都是有限制的,这样的话有可能导致提交失败,或者是用户体验很差,数据要传递很久才全部传送到服务器。
因此,在富文本编辑的过程中,对于图片的处理,我们更合理的做法是将图片上传到服务器,再将图片链接插入到富文本中,以达到最优的体验。
废话不多说,接下来直接看如何改造

改造分析

查阅网上的资料,我感觉提供的方案都不是特别友好,网上搜索的基本都是这一个方法
配合 element-ui 实现上传图片/视频到七牛或者是直接重新写一个按钮来进行自定义图片操作

坦白讲,上面这2个方法都很特别,也的确有效果,但是我个人还是觉得不完美,第一个方法写得太麻烦,第二个方法有点投机取巧。
结合上面两种方法以及官方的文档,我这里提供一个新的改造思路给大家参考。

引入element-ui

和第一种方法类似,为了更好的控制上传的图片,我这里也是引用了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

这里对于如何安装和引入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这个设置

重写点击图片按钮事件

从下图可以看到,默认的配置中,整个工具栏具备了所有的功能,自然也包括红圈中的图片上传功能了。
那么接下来我们要怎么去重写这个按钮的事件呢。
clipboard.png

很简单,我们需要在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哦。
如果有错误,欢迎大家多提提意见,希望这篇文章能帮到有需要的人。

基于promise的图片资源加载

基于promise的图片资源一次性加载或者预加载

场景描述

不是每个网页端的用户都能用得起光纤,不是每张图片都是压缩得很小,有时候我们也想要看高清大图,但是受限于网速有时候场景是这样的:(很明显左边的第一张图片还没出来,其他的就出来了)

图片资源预加载是一个很常见的需求,在网页开发中,

  • 譬如我们在开发一个基于canvas的游戏,涉及到图片资源的时候,为了游戏的体验,我们希望图片资源全部加载才开始游戏。
  • 又譬如在一个多图展示的网页,由于图片过多或图片太大,我们希望图片加载完再一次性显示,而不是东一张西一张陆续显示,这时候也需要用图片一次性加载功能。

图片资源加载的原理

在浏览器向服务器发送请求的过程中,如果图片资源已经加载过一次了,则不会再从服务器加载同一个图片,
利用这个原理,我们的实现思路如下:

// 创建一个图片对象
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个参数键值

  • config.sourceData: 必选参数,资源对象 {key: value} 资源名:资源地址
  • config.target:可选参数,要实现加载的目标节点集
  • config.response:可选参数,默认false,不返回异步对象;设置为true,则返回,可通过then()进行下一步数据操作
  • config.mode: 可选参数,是否开启严格模式
    • 值为false(默认值): 不开启严格模式,该模式下如果有部分图片加载失败,其余图片仍然显示
    • 值为true: 开启严格模式,该模式下如果有部分图片加载失败,所有图片都不显示,如果需要返回结果,则结果也是空对象{}

基本调用方式如下

// 图片资源对象, 对象的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  // 是否返回异步对象
})

应用实例

  • canvas中加载全部图片再绘制到canvas面板中

    记住必须设置config.response 的值为 true 才能用then进行数据操作
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页加载完毕一次性展示全部图片实例

<!--在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,  // 是否开启严格模式
})
  • loadImg()如何在vue中优雅地实现图片加载

在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
    }
}

vue-quill-editor-config.js

// 图片上传参数配置
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  // 事件重写
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.