H5 图片上传组件

2,538 阅读2分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

H5 图片上传组件:可单独使用,也可以嵌入 android 原生壳子中,与手机端进行交互使用。基于 input 进行组件封装,完成权限问题、图片上传、图片压缩、图片 转 base64 格式,base64 转 Blob 等

  1. 权限问题:相机+存储双权限,相机拍照,相册获取
  2. 图片上传:可批量上传,同时传多张图片
  3. 图片压缩:使用 canvas 压缩处理
  4. 图片预览:图片数据转为 base64 格式
  5. 重复图片无法上传(清除 input 的值)

组件封装

<template>
  <div @click.stop="chooseType" class="upload-wrap">
    <slot name="upload">
      <div class="upload-item">
        <img src="/images/icon_upload.png" class="item-img" />
      </div>
    </slot>
    <input
      id="uploadIcon"
      ref="uploadIcon"
      @change="uploadImg($event)"
      type="file"
      accept="image/*"
      style="display: none;"
      capture="camera"
    />
  </div>
</template>

<script>
  export default {
    name: "UploadImage",
    props: {
      quality: {
        // 图片质量
        type: Number,
        default: null,
      },
      width: {
        // 图片宽度
        type: Number,
        default: null,
      },
      height: {
        // 图片高度
        type: Number,
        default: null,
      },
    },
    data() {
      return {
        picList: [],
      };
    },
    methods: {},
  };
</script>

<style lang="scss" scoped>
  .upload-wrap {
    .upload-item {
      width: 1.68rem;
      height: 1.68rem;
      border-radius: 0.12rem;
      margin-right: 0.2rem;
      position: relative;
      overflow: hidden;

      .item-img {
        width: 100%;
        height: 100%;
      }
    }
  }
</style>

权限问题

需要与客户端进行交互,获取权限。我们这边的权限参数是客户端挂载在 window 上的

export default {
  computed: {
    isApp() {
      return this.$store.state.nativePara.isApp;
    },
  },
  methods: {
    // 点击之后,首先进行权限判断
    chooseType() {
      // 客户端交互 获取权限
      if (this.getPermession()) {
        // 通过校验后,主动触发 input 的 click 事件
        document.getElementById("uploadIcon").click();
      }
    },
    getPermession() {
      // 如果是在app中
      if (this.isApp) {
        // 获取照相机权限
        const permission = window.xxx.checkPermission(
          "android.permission.CAMERA"
        );
        if (permission === 0) {
          // 没开的话,申请开启
          window.xxx.requestPermission("android.permission.CAMERA");
          return false;
        }

        // 本地存储权限
        const permission2 = window.xxx.checkPermission(
          "android.permission.WRITE_EXTERNAL_STORAGE"
        );
        if (permission2 === 0) {
          // 没开的话,申请开启
          window.xxx.requestPermission(
            "android.permission.WRITE_EXTERNAL_STORAGE"
          );
          return false;
        }
      }
      return true;
    },
  },
};

图片上传

  1. 检查:检查文件是否被选中;文件是否过大;文件类型是否正确
  2. 处理:图片压缩,base64 转化
export default {
  methods: {
    uploadImg(e) {
      // 检查是否有文件被选中
      const files = e.target.files || e.dataTransfer.files;
      if (!files.length) {
        return;
      }

      // 检查文件是否过大
      const file = files[0];
      if (file.size / 1024 > 5000) {
        this.$toast.show("图片太呀,请压缩后再上传", {
          duration: 3000,
        });
        return;
      }

      // 判断文件类型:必须是图片才处理
      if (!/^image\//.test(file.type)) {
        this.$toast.show("请上传图片", { duration: 3000 });
        // 不是图片的话,清除数据
        document.getElementById("uploadIcon").value = "";
        return;
      }

      this.convertBase64(file, (blob) => {
        const data = this.compress(blob); // 压缩照片
        console.log('压缩后的图片大小',data.length)
        const imageData = {
          time: new Date().getTime(),
          uri: data,
        };
        this.$emit("on-upload", imageData);
      });
    },
  },
};

图片预览

图片预览:将图片数据转为 base64 格式,返回给使用者

  1. FileReader.readAsDataURL(file)获取 data:base64 的字符串。同步执行(立即)
  2. URL.createObjectURL(blob)获取当前文件的一个内存 URL,表示指定的 File 对象或 Blob 对象。异步执行(需要时间)
export default {
  methods: {
    convertBase64(file, callback) {
      // 判断是否支持FileReader
      if (!window.FileReader) {
        const URL = window.URL || window.webkitURL;
        const blob = URL.createObjectURL(file); // 获取照片的文件流
        if (callback) {
          callback(blob);
        }
        return;
      }

      // 创建一个reader
      const reader = new FileReader();
      // 将图片转成base64格式
      reader.readAsDataURL(file);
      // 读取成功后的回调
      reader.onloadend = (e) => {
        const result = e.target.result;

        // 解决重复图片上传无效问题:先清除
        document.getElementById("uploadIcon").value = "";

        // 图片处理
        const img = new Image();
        img.src = result;

        console.log("未压缩图片", result.length);

        img.onload = () => {
          if (callback) {
            callback(img);
          }
        };
      };
    },
  },
};

图片压缩

export default {
  methods: {
    compress(img) {
      // 默认按比例压缩
      const width = img.width;
      const height = img.height;
      const scale = width / height;
      // 生成canvas
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      canvas.width = this.width || width;
      canvas.height = this.height || width / scale || height;
      // 铺底色
      ctx.fillStyle = "#fff";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, width, height);

      // quality值越小,所绘制出的图像越模糊。
      let quality = 0.1; // 默认图片质量为0.1,进行最小压缩
      if (this.quality && this.quality <= 1 && this.quality > 0) {
        quality = this.quality;
      }
      const base64 = canvas.toDataURL("image/jpeg", quality);
      return base64;
    },
  },
};

base64 转 Blob

将以 base64 的图片 url 数据转换为 Blob

export default {
  methods: {
    dataURLtoBlob(dataUrl) {
      const arr = dataUrl.split(",");
      const mime = arr[0].match(/:(.*?);/)[1];
      const bStr = atob(arr[1]);
      let n = bStr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bStr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    },
  },
};