Skip to content

图片裁剪&压缩

需求

  • 1、用户上传头像后可以自定义裁剪
  • 2、头像大小超过5M时需要进行压缩上传

裁剪实现方案

  • 1、获取到用户需要上传的头像,通过FileReader转换base64
  • 2、实例化一个new Image(),监听load事件
  • 3、根据画布的大小,和图片本身实际大小计算缩放的比例
  • 4、创建一个canvas对象,通过首先清空画布,通过drawImage把图片渲染在canvas上,从(0,0)坐标点
  • 5、在画布容器中定义一个蒙版,当蒙版位置发生改变时,计算蒙版(x,y)到canvas(0,0)坐标点距离
  • 6、通过canvas对象中的getImageData,加上(x,y)坐标点距离,获取被裁剪的图片信息
  • 7、在创建一个canvas对象,调用putImageData,最后通过canvas把图片转换成base64、或者是Blob
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>图片裁剪</title>
</head>
<style>
  .box {
    position: relative;
  }

  #canvas {
    border: 1px solid red;
    box-sizing: border-box;
  }

  #img {
    width: 100px;
    height: 100px;
    border: 1px solid red;
    box-sizing: border-box;
  }

  .operation {
    width: 100px;
    height: 100px;
    background: gold;
    position: absolute;
    opacity: 0.2;
    left: 0;
    top: 0;
  }
</style>

<body>
  <input type="file" id="upload" />
  <div class="box">
    <canvas id="canvas" width="300" height="300">
    </canvas>
    <div class="operation"></div>
    <div>
      <button onclick="zoomIn()">放大</button>
      <button onclick="zoomOut()">缩小</button>
      <button onclick="cropping()">裁剪</button>
    </div>
  </div>
  <img id="img"></img>
</body>
<script>
  const canvas = document.querySelector( "#canvas" );
  const ctx = canvas.getContext( "2d" );
  const upload = document.getElementById( "upload" );
  const img = document.getElementById( "img" );
  const operation = document.querySelector( ".operation" );

  let base64Image;
  let times = 1; //图片放大的倍数
  const ACCEPT = [ "image/jpg", "image/png", "image/jpeg" ];
  const MAXSIZE = 10 * 1024 * 1024;
  const MAXSIZE_STR = "10MB";
  // Base64
  function convertImageToBase64 ( file ) {
    return new Promise( ( resolve, reject ) => {
      let reader = new FileReader();
      reader.addEventListener( "load", ( e ) => {
        const base64Image = e.target.result;
        resolve( base64Image );
        reader = null;
      } );
      reader.readAsDataURL( file );
    } );
  }

  // 图片等比缩放
  function scale ( maxW, maxH, orgW, orgH ) {
    let w;
    let h;
    if ( orgW <= maxW && orgH <= maxH ) {
      // 1.宽高未超过最大尺寸=>使用原始尺寸
      w = orgW;
      h = orgH;
    } else if ( orgW > maxW && orgH > maxH ) {
      // 2.宽高超过最大尺寸
      let ratioH = orgH / maxH;
      let ratioW = orgW / maxW;

      if ( ratioH === ratioW ) {
        // 2.1 缩放比例相等,宽高都需要缩放
        w = orgW / ratioW;
        h = orgH / ratioH;
      } else if ( ratioH > ratioW ) {
        // 2.2 高的缩放比大于宽的缩放比=>高等于最大高,宽需要按照比例缩放
        h = maxH;
        w = orgW / ratioH;
      } else {
        // 2.2 宽的缩放比大于高的缩放比=>宽等于最大宽,高需要按照比例缩放
        w = maxW;
        h = orgH / ratioW;
      }
    } else if ( orgW > maxW ) {
      // 3.高未超,宽超过最大尺寸=>宽等于最大宽,高需要按照比例缩放
      let ratio = orgW / maxW;
      w = maxW;
      h = parseInt( orgH / ratio );
    } else {
      // 4.宽未超,高超过最大尺寸=>高等于最大高,宽需要按照比例缩放
      let ratio = orgH / maxH;
      h = maxH;
      w = parseInt( orgW / ratio );
    }
    return {
      w,
      h,
    };
  }
  // 绘制图片
  function draw ( base64Image, x = 0, y = 0 ) {
    const image = new Image();
    image.addEventListener( "load", function ( e ) {
      let maxW = 300; // 最大画布宽
      let maxH = 300; // 最大画布高
      let { w, h } = scale(
        maxW,
        maxH,
        image.naturalWidth,
        image.naturalHeight
      );
      ctx.clearRect( 0, 0, maxW, maxH ); // 清空画布
      ctx.drawImage( image, x, y, w * times, h * times ); // 绘制图片
    } );
    image.src = base64Image;
  }
  // 放大
  function zoomIn () {
    times += 0.1;
    draw( base64Image );
  }
  // 缩小
  function zoomOut () {
    times -= 0.1;
    draw( base64Image );
  }
  // 裁剪
  function cropping () {
    let x = parseInt( operation.style.left || 0 )
    let y = parseInt( operation.style.top || 0 )
    const imageData = ctx.getImageData( x, y, 100, 100 ); //获取头像数据
    let clipCanvas = document.createElement( "canvas" );
    clipCanvas.width = 100;
    clipCanvas.height = 100;
    const clipContext = clipCanvas.getContext( "2d" );
    clipContext.putImageData( imageData, 0, 0 );
    let dataUrl = clipCanvas.toDataURL();
    img.setAttribute( 'src', dataUrl )
  }

  upload.addEventListener( "change", async ( e ) => {
    const [ file ] = e.target.files;
    if ( !file ) {
      return;
    }
    const { type: fileType, size: fileSize } = file;
    if ( !ACCEPT.includes( fileType ) ) {
      alert( `不支持[${ fileType }]文件类型!` );
      upload.value = "";
      return;
    }

    if ( fileSize > MAXSIZE ) {
      alert( `文件超出${ MAXSIZE_STR }!` );
      upload.value = "";
      return;
    }

    base64Image = await convertImageToBase64( file );
    draw( base64Image );
  } );

  function move ( ele, callback ) {
    let isMove = false;
    let startX = 0;
    let startY = 0;
    // 按下
    function startMove ( e ) {
      console.log( 'mousedown' )
      isMove = true;
      let { left, top } = ele.getBoundingClientRect(); // 视窗的距离
      startX = e.pageX - left;
      startY = e.pageY - top;
      console.log(startX)
      // 移动
      document.addEventListener( "mousemove", handleMove );
      // 释放
      document.addEventListener( "mouseup", handleMouseUp );

    }
    function handleMove ( e ) {
      console.log( 'mousemove' )
      if ( isMove ) {
        console.log( 'mousemove' )
        callback && callback( e.pageX - startX, e.pageY - startY )
      }

    }

    function handleMouseUp ( e ) {
      if ( isMove ) {
        console.log( 'mouseup' )
        isMove = false;
      }
      // 解绑事件
      document.removeEventListener( "mousemove", handleMove );
      document.removeEventListener( "mouseup", handleMouseUp );

    }

    ele.addEventListener( "mousedown", startMove );

  }
  move( canvas, ( x, y ) => {
    draw(
      base64Image,
      x,
      y
    );
  } )

  move( operation, ( x, y ) => {
    operation.style.left = x + 'px'
    operation.style.top = y + 'px'
  } )

</script>

</html>

图片压缩实现(简单版本)

  • 1、获取到用户需要上传的头像,通过FileReader转换base64
  • 2、实例化一个new Image(),监听load事件
  • 3、创建一个canvas对象,通过首先清空画布,通过drawImage把图片渲染在canvas
  • 4、最后 canvas.toDataURL("image/jpeg", 0.5)、或者toBlob