移动端图片裁剪上传

移动端图片裁剪上传

  1. clip.css
img {
  display: block;
  margin: 0 auto;
}
input[type="file"] {
  outline: none;
  margin-top: 20px;
}
* {
  margin: 0;
  padding: 0;
}
#clip_button {
  position: absolute;
  right: 10%;
  bottom: 50px;
  width: 120px;
  height: 60px;
  border: none;
  border-radius: 2px;
  background: #1aad19;
  color: #fff;
}
#cancel_clip {
  position: absolute;
  left: 10%;
  bottom: 50px;
  width: 120px;
  height: 60px;
  border: none;
  border-radius: 2px;
  color: #fff;
  background: #e64340;
}
#clip_container.container {
  z-index: 1000;
  position: fixed;
  padding-top: 60px;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 1);
}
#clip_container.container > div {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}
#clip_image {
  max-width: 100%;
}

.cropper-container {
  font-size: 0;
  line-height: 0;
  position: relative;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  direction: ltr;
  -ms-touch-action: none;
  touch-action: none;
}
.crop_loading,
.crop_success {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9;
}
.crop_loading .crop_content {
  position: absolute;
  top: 50%;
  left: 50%;
  text-align: center;
  background: #000;
  opacity: 0.9;
  height: 100px;
  width: 100px;
  vertical-align: middle;
  color: #fff;
  padding-top: 20px;
  font-size: 16px;
  -webkit-border-radius: 3px;
  border-radius: 3px;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}
.crop_loading .crop_content img {
  margin-top: 15px;
  margin-bottom: 10px;
}
.crop_success .crop_success_text {
  position: absolute;
  top: 50%;
  left: 50%;
  text-align: center;
  background: #000;
  opacity: 0.9;
  width: 120px;
  height: 30px;
  color: #fff;
  line-height: 30px;
  font-size: 16px;
  -webkit-border-radius: 3px;
  border-radius: 3px;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}
.cropper-container img {
  /* Avoid margin top issue (Occur only when margin-top <= -height) */
  display: block;
  min-width: 0 !important;
  max-width: none !important;
  min-height: 0 !important;
  max-height: none !important;
  width: 100%;
  height: 100%;
  image-orientation: 0deg;
}

.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.cropper-wrap-box {
  overflow: hidden;
}

.cropper-drag-box {
  opacity: 0;
  background-color: #fff;
}

.cropper-modal {
  opacity: 0.5;
  background-color: #000;
}

.cropper-view-box {
  display: block;
  overflow: hidden;

  width: 100%;
  height: 100%;

  outline: 1px solid #39f;
  outline-color: rgba(51, 153, 255, 0.75);
}

.cropper-dashed {
  position: absolute;

  display: block;

  opacity: 0.5;
  border: 0 dashed #eee;
}

.cropper-dashed.dashed-h {
  top: 33.33333%;
  left: 0;
  width: 100%;
  height: 33.33333%;
  border-top-width: 1px;
  border-bottom-width: 1px;
}

.cropper-dashed.dashed-v {
  top: 0;
  left: 33.33333%;
  width: 33.33333%;
  height: 100%;
  border-right-width: 1px;
  border-left-width: 1px;
}

.cropper-center {
  position: absolute;
  top: 50%;
  left: 50%;

  display: block;

  width: 0;
  height: 0;

  opacity: 0.75;
}

.cropper-center:before,
.cropper-center:after {
  position: absolute;
  display: block;
  content: " ";
  background-color: #eee;
}

.cropper-center:before {
  top: 0;
  left: -3px;
  width: 7px;
  height: 1px;
}

.cropper-center:after {
  top: -3px;
  left: 0;
  width: 1px;
  height: 7px;
}

.cropper-face,
.cropper-line,
.cropper-point {
  position: absolute;

  display: block;

  width: 100%;
  height: 100%;

  opacity: 0.1;
}

.cropper-face {
  top: 0;
  left: 0;

  background-color: #fff;
}

.cropper-line {
  background-color: #39f;
}

.cropper-line.line-e {
  top: 0;
  right: -3px;
  width: 5px;
  cursor: e-resize;
}

.cropper-line.line-n {
  top: -3px;
  left: 0;
  height: 5px;
  cursor: n-resize;
}

.cropper-line.line-w {
  top: 0;
  left: -3px;
  width: 5px;
  cursor: w-resize;
}

.cropper-line.line-s {
  bottom: -3px;
  left: 0;
  height: 5px;
  cursor: s-resize;
}

.cropper-point {
  width: 5px;
  height: 5px;

  opacity: 0.75;
  background-color: #39f;
}

.cropper-point.point-e {
  top: 50%;
  right: -3px;
  margin-top: -3px;
  cursor: e-resize;
}

.cropper-point.point-n {
  top: -3px;
  left: 50%;
  margin-left: -3px;
  cursor: n-resize;
}

.cropper-point.point-w {
  top: 50%;
  left: -3px;
  margin-top: -3px;
  cursor: w-resize;
}

.cropper-point.point-s {
  bottom: -3px;
  left: 50%;
  margin-left: -3px;
  cursor: s-resize;
}

.cropper-point.point-ne {
  top: -3px;
  right: -3px;
  cursor: ne-resize;
}

.cropper-point.point-nw {
  top: -3px;
  left: -3px;
  cursor: nw-resize;
}

.cropper-point.point-sw {
  bottom: -3px;
  left: -3px;
  cursor: sw-resize;
}

.cropper-point.point-se {
  right: -3px;
  bottom: -3px;
  width: 20px;
  height: 20px;
  cursor: se-resize;
  opacity: 1;
}

@media (min-width: 768px) {
  .cropper-point.point-se {
    width: 15px;
    height: 15px;
  }
}

@media (min-width: 992px) {
  .cropper-point.point-se {
    width: 10px;
    height: 10px;
  }
}

@media (min-width: 1200px) {
  .cropper-point.point-se {
    width: 5px;
    height: 5px;
    opacity: 0.75;
  }
}

.cropper-point.point-se:before {
  position: absolute;
  right: -50%;
  bottom: -50%;
  display: block;
  width: 200%;
  height: 200%;
  content: " ";
  opacity: 0;
  background-color: #39f;
}

.cropper-invisible {
  opacity: 0;
}

.cropper-bg {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMzTjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
}

.cropper-hide {
  position: absolute;

  display: block;

  width: 0;
  height: 0;
}

.cropper-hidden {
  display: none !important;
}

.cropper-move {
  cursor: move;
}

.cropper-crop {
  cursor: crosshair;
}

.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
  cursor: not-allowed;
}

  1. 环境
"cropperjs": "^1.0.0-rc.2"
  1. clipper.js
/**
 *	name : vue 的图片裁减压缩处理
 *   author : 陈威
 *   date : 2017/6/20
 *   dependence :
 *       cropper 第三方的一个裁剪库
 *       exif    获取图片源信息,解决ios图片旋转的问题
 * */
import Cropper from "cropperjs";
import store from "../store";
import { Toast } from "vant";
// import Exif from 'exif-js'
export default {
  install(app) {
    //初始化方法
    app.config.globalProperties.initilize = function (opt) {
      let self = this;
      this.options = opt;
      //创建dom
      this.createElement();
      this.resultObj = opt.resultObj;
      //初始化裁剪对象
      this.cropper = new Cropper(this.preview, {
        aspectRatio: opt.aspectRatio || 1,
        autoCropArea: opt.autoCropArea || 0.8,
        viewMode: 1,
        guides: opt.aspectRatio == "Free" ? false : true,
        cropBoxResizable: opt.aspectRatio == "Free" ? false : true,
        cropBoxMovable: opt.aspectRatio == "Free" ? false : true,
        dragCrop: opt.aspectRatio == "Free" ? false : true,
        background: false,
        checkOrientation: true,
        checkCrossOrigin: true,
        zoomable: false,
        zoomOnWheel: false,
        center: false,
        toggleDragModeOnDblclick: false,
        ready: function () {
          // console.log(self.cropper.rotate(90))
          if (opt.aspectRatio == "Free") {
            let cropBox = self.cropper.cropBox;
            cropBox.querySelector("span.cropper-view-box").style.outline =
              "none";
            self.cropper.disable();
          }
        },
      });
    };
    //创建一些必要的DOM,用于图片裁剪
    app.config.globalProperties.createElement = function () {
      //初始化图片为空对象
      this.preview = null;

      let str =
        '<div><img id="clip_image" src="originUrl"></div><button type="button" id="cancel_clip">取消</button><button type="button" id="clip_button">确定</button>';
      let body = document.getElementsByTagName("body")[0];
      this.reagion = document.createElement("div");
      this.reagion.id = "clip_container";
      this.reagion.className = "container";
      this.reagion.innerHTML = str;
      //添加创建好的DOM元素
      body.appendChild(this.reagion);
      this.preview = document.getElementById("clip_image");

      //绑定一些方法
      this.initFunction();
    };
    //初始化一些函数绑定
    app.config.globalProperties.initFunction = function () {
      let self = this;
      this.clickBtn = document.getElementById("clip_button");
      this.cancelBtn = document.getElementById("cancel_clip");
      //确定事件
      this.addEvent(this.clickBtn, "click", function () {
        self.crop();
      });
      //取消事件
      this.addEvent(this.cancelBtn, "click", function () {
        self.destoried();
      });
      //清空input的值
      this.addEvent(this.fileObj, "click", function () {
        this.value = "";
      });
    };

    //外部接口,用于input['file']对象change时的调用
    app.config.globalProperties.clip = function (e, opt) {
      // let self = this;

      this.fileObj = e.srcElement;

      let files = e.target.files || e.dataTransfer.files;

      if (!files.length) return false; //不是图片直接返回

      //调用初始化方法
      this.initilize(opt);

      //获取图片文件资源
      this.picValue = files[0];

      //去获取拍照时的信息,解决拍出来的照片旋转问题
      // Exif.getData( files[0] , function(){
      //   self.Orientation = Exif.getTag( files[0], 'Orientation');
      //   console.log(self.Orientation)
      // });

      //调用方法转成url格式
      this.originUrl = this.getObjectURL(this.picValue);

      //每次替换图片要重新得到新的url
      if (this.cropper) {
        this.cropper.replace(this.originUrl);
      }
    };
    //图片转码方法
    app.config.globalProperties.getObjectURL = function (file) {
      let url = null;
      if (window.createObjectURL != undefined) {
        // basic
        url = window.createObjectURL(file);
      } else if (window.URL != undefined) {
        // mozilla(firefox)
        url = window.URL.createObjectURL(file);
      } else if (window.webkitURL != undefined) {
        // webkit or chrome
        url = window.webkitURL.createObjectURL(file);
      }
      return url;
    };

    app.config.globalProperties.setUserInfo = function (key, val) {
      this.$api.user.modifyUser({ avatar: val }).then((r) => {
        let userInfo = JSON.parse(sessionStorage.getItem("userInfo"));
        userInfo[key] = val;
        store.dispatch("userInfo/setUserInfo", userInfo);
        Toast.success(r.message);
      });
    };

    //点击确定进行裁剪
    app.config.globalProperties.crop = function () {
      let self = this;
      let image = new Image();
      let croppedCanvas;
      let roundedCanvas;

      // Crop
      // document.querySelector(".crop_loading").style.display = "block";

      setTimeout(function () {
        croppedCanvas = self.cropper.getCroppedCanvas();
        // Round
        roundedCanvas = self.getRoundedCanvas(croppedCanvas);

        let imgData = roundedCanvas.toDataURL();
        image.src = imgData;
        console.log(imgData.length);
        //判断图片是否大于100k,不大于直接上传,反之压缩
        if (imgData.length < 50 * 1024) {
          self.resultObj.src = imgData;
          //图片上传
          self.setUserInfo("avatar", imgData);
          self.postImg(imgData);
        } else {
          image.onload = function () {
            //压缩处理
            let data = self.compress(image, self.Orientation);
            self.setUserInfo("avatar", data);
            self.resultObj.src = data;
            console.log(data.length);
            //图片上传
            self.postImg(data);
          };
        }
      }, 20);
    };
    //获取裁剪图片资源
    app.config.globalProperties.getRoundedCanvas = function (sourceCanvas) {
      let canvas = document.createElement("canvas");
      let context = canvas.getContext("2d");
      let width = sourceCanvas.width;
      let height = sourceCanvas.height;

      canvas.width = width;
      canvas.height = height;

      context.imageSmoothingEnabled = true;
      context.drawImage(sourceCanvas, 0, 0, width, height);
      context.globalCompositeOperation = "destination-in";
      context.beginPath();
      context.rect(0, 0, width, height);
      context.fill();

      return canvas;
    };
    //销毁原来的对象
    app.config.globalProperties.destoried = function () {
      // let self = this;
      //移除事件
      this.removeEvent(this.clickBtn, "click", null);
      this.removeEvent(this.cancelBtn, "click", null);
      this.removeEvent(this.fileObj, "click", null);
      //移除裁剪框
      this.reagion.parentNode.removeChild(this.reagion);

      //销毁裁剪对象
      this.cropper.destroy();
      this.cropper = null;
    };
    //图片上传
    app.config.globalProperties.postImg = function () {
      //这边写图片的上传
      let self = this;
      self.destoried();

      window.setTimeout(function () {
        // document.querySelector(".crop_success").style.display = "block";
        //裁剪完后摧毁对象
        self.destoried();
      }, 3000);
    };

    //图片旋转
    app.config.globalProperties.rotateImg = function (img, direction, canvas) {
      //最小与最大旋转方向,图片旋转4次后回到原方向
      const min_step = 0;
      const max_step = 3;
      if (img == null) return;
      //img的高度和宽度不能在img元素隐藏后获取,否则会出错
      let height = img.height;
      let width = img.width;
      let step = 2;
      if (step == null) {
        step = min_step;
      }
      if (direction == "right") {
        step++;
        //旋转到原位置,即超过最大值
        step > max_step && (step = min_step);
      } else {
        step--;
        step < min_step && (step = max_step);
      }
      //旋转角度以弧度值为参数
      let degree = (step * 90 * Math.PI) / 180;
      let ctx = canvas.getContext("2d");
      switch (step) {
        case 0:
          canvas.width = width;
          canvas.height = height;
          ctx.drawImage(img, 0, 0);
          break;
        case 1:
          canvas.width = height;
          canvas.height = width;
          ctx.rotate(degree);
          ctx.drawImage(img, 0, -height);
          break;
        case 2:
          canvas.width = width;
          canvas.height = height;
          ctx.rotate(degree);
          ctx.drawImage(img, -width, -height);
          break;
        case 3:
          canvas.width = height;
          canvas.height = width;
          ctx.rotate(degree);
          ctx.drawImage(img, -width, 0);
          break;
      }
    };

    //图片压缩
    app.config.globalProperties.compress = function (img, Orientation) {
      let canvas = document.createElement("canvas");
      let ctx = canvas.getContext("2d");
      //瓦片canvas
      let tCanvas = document.createElement("canvas");
      let tctx = tCanvas.getContext("2d");
      let initSize = img.src.length;
      let width = img.width;
      let height = img.height;

      //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
      let ratio;
      if ((ratio = (width * height) / 4000000) > 1) {
        console.log("大于400万像素");
        ratio = Math.sqrt(ratio);
        width /= ratio;
        height /= ratio;
      } else {
        ratio = 1;
      }
      canvas.width = width;
      canvas.height = height;
      //        铺底色
      ctx.fillStyle = "#fff";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      //如果图片像素大于100万则使用瓦片绘制
      let count;
      if ((count = (width * height) / 1000000) > 1) {
        count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片
        //            计算每块瓦片的宽和高
        let nw = ~~(width / count);
        let nh = ~~(height / count);
        tCanvas.width = nw;
        tCanvas.height = nh;
        for (let i = 0; i < count; i++) {
          for (let j = 0; j < count; j++) {
            tctx.drawImage(
              img,
              i * nw * ratio,
              j * nh * ratio,
              nw * ratio,
              nh * ratio,
              0,
              0,
              nw,
              nh
            );
            ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
          }
        }
      } else {
        ctx.drawImage(img, 0, 0, width, height);
      }
      //修复ios上传图片的时候 被旋转的问题
      if (Orientation != "" && Orientation != 1) {
        switch (Orientation) {
          case 6: //需要顺时针(向左)90度旋转
            this.rotateImg(img, "left", canvas);
            break;
          case 8: //需要逆时针(向右)90度旋转
            this.rotateImg(img, "right", canvas);
            break;
          case 3: //需要180度旋转
            this.rotateImg(img, "right", canvas); //转两次
            this.rotateImg(img, "right", canvas);
            break;
        }
      }
      //进行最小压缩
      let ndata = canvas.toDataURL("image/jpeg", 0.01);
      console.log("压缩前:" + initSize);
      console.log("压缩后:" + ndata.length);
      console.log(
        "压缩率:" + ~~((100 * (initSize - ndata.length)) / initSize) + "%"
      );
      tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;

      return ndata;
    };

    //添加事件
    app.config.globalProperties.addEvent = function (obj, type, fn) {
      if (obj.addEventListener) {
        obj.addEventListener(type, fn, false);
      } else {
        obj.attachEvent("on" + type, fn);
      }
    };
    //移除事件
    app.config.globalProperties.removeEvent = function (obj, type, fn) {
      if (obj.removeEventListener) {
        obj.removeEventListener(type, fn, false);
      } else {
        obj.detachEvent("on" + type, fn);
      }
    };
  },
};

  1. index.scss
@import url("./clip.scss");
  1. main.js
// 全局样式
import "./assets/styles/index.scss";

import clipper from "./utils/clipper";
app.use(clipper);
  1. 使用(vue2)
<template>
  <div>
    <van-nav-bar
      left-arrow
      title="修改头像"
      @click-left="
        () => {
          $router.go(-1);
        }
      "
    />
    <div class="headImgChange">
      <div>
        <div class="image">
          <img :src="userInfo.avatar" alt="" id="img" />
        </div>
        <div class="btns">
          <van-button round @click="$refs.imgInput.click()"
            >从本地选择一张图片</van-button
          >
          <input
            ref="imgInput"
            type="file"
            accept="image/png,image/jpg,image/jpeg"
            @change="change($event)"
            v-show="false"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      imageUrl: require("@/assets/images/head.svg"),
    };
  },
  computed: {
    userInfo() {
      return this.$store.state.userInfo.data;
    },
  },
  methods: {
    change(event) {
      let image = document.getElementById("img"); //预览对象
      this.clip(event, {
        resultObj: image,
        aspectRatio: 1,
      });
    },
  },
};
</script>
<style lang="scss" scoped>
.headImgChange {
  width: 100vw;
  height: 500px;
  overflow: auto;
  box-sizing: border-box;
  .btns {
    display: flex;
    justify-content: center;
    margin: 20px 0;
    .van-button {
      padding: 0 120px;
    }
  }
  .image {
    width: 100%;
    padding: 20px 0;
    display: flex;
    justify-content: center;
    align-items: center;
    img {
      width: 90%;
      // border-radius: 50%;
    }
  }
}
</style>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值