html 头像裁剪框

稍微改改就能使用了,这里预览直接在裁剪框canvas上显示了,

可以直接使用canvas.toBlob((result)=>{

    //result 就是blob  使用formdata包装一下就可以使用ajax等技术上传了

const formData = new FormData();

formData.append("file", result, "图像名称.jpg");

});

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
  <title>图片裁剪</title>
  <style type="text/css">
  body {
    overflow: hidden;
  }

  .wrapper {
    position: relative;
    display: flex;
    width: 100%;
    max-width: 500px;
  }

  .img-box {
    position: relative;
    width: 100%;
    height: 80vh;
    overflow: hidden;
    /*aspect-ratio: 1 / 1;*/
    display: flex;
  }

  .img-box img {
    /*注意这里需要设置右外边距和地外边距自动(auto)否则改变 上外边距或左外边距可能造成缩放效果*/
    margin: 0 auto auto 0;
  }

  .drop-wrapper {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /*background-color: rgba(0, 0, 0, 0.5);*/
    pointer-events: none;
    overflow: hidden;
    border: none;
  }

  #drop-canvas {
    box-shadow: 0 0 0 500px rgba(0, 0, 0, 0.5);
    pointer-events: none;
  }

  </style>
</head>
<body>
<div style="overflow: hidden;">
  <input type="file" name="file" onchange="fileChange(this)">
  <button onclick="toShow()">预览裁剪的图片</button>
  <br>
  <div class="wrapper">
    <div class="img-box">
      <img src="" id="img">
    </div>
    <div class="drop-wrapper">
      <canvas id="drop-canvas" width="300" height="300"></canvas>
    </div>
  </div>

</div>

<script type="text/javascript">
const image = document.getElementById('img');
const dropWrapper = document.querySelector(".drop-wrapper");
const dropCanvas = document.getElementById("drop-canvas");
const dropCtx = dropCanvas.getContext("2d");
let isInit = false;
let ratio = 1.0, x = 0, y = 0;
const dropLeft = (dropWrapper.clientWidth - 300) / 2;
const dropTop = (dropWrapper.clientHeight - 300) / 2;

function fileChange(e) {
isInit = false; console.log(e.files[
0]); const file = e.files[0]; const reader = new FileReader(); reader.onload = function () { image.onload = () => { initImageHandle(); }; image.src = reader.result; }; reader.readAsDataURL(file); e.value = ''; } // 初始化图片后的一些操作 // 图片居中 function initImageHandle() { if (image.naturalWidth < 100 || image.naturalHeight < 100) { alert('选择的图片最小宽高为100*100'); image.src = ''; return; } initRatioHandle(); // 修正边界问题 handleZoomBoundary(); handleCenter(); } // 初始处理合理的缩放比例 function initRatioHandle() { const originalWidth = image.naturalWidth; const originalHeight = image.naturalHeight; if (originalWidth < 300 || originalHeight < 300) { if (originalWidth > originalHeight) { ratio = originalHeight / 300; } else { ratio = originalWidth / 300; } } if (originalWidth > 600 && originalHeight > 600) { if (originalWidth > originalHeight) { ratio = 600 / originalHeight; } else { ratio = 600 / originalWidth; } } isInit = true; } // 缩小 function zoomOut(decr = 0.01) { if(isInit === false) return; let _ratio = ratio - decr; if (_ratio <= 0) _ratio = 0.01; let width = image.naturalWidth * _ratio; let height = image.naturalHeight * _ratio; if (width < 200 || height < 200) { if (width > height) { height = 200; _ratio = 200 / image.naturalHeight; width = image.naturalWidth * _ratio; } else { width = 200; _ratio = 200 / image.naturalWidth; height = image.naturalHeight * _ratio; } } const x = image.naturalWidth * (ratio - _ratio) / 2; const y = image.naturalHeight * (ratio - _ratio) / 2; ratio = _ratio; image.style.width = width + 'px'; image.style.height = height + 'px'; marginLeft = marginLeft + x; marginTop = marginTop + y; image.style.marginLeft = marginLeft + 'px'; image.style.marginTop = marginTop + 'px'; } // 放大 function zoomIn(incr = 0.01) { if(isInit === false) return; let _ratio = ratio + incr; if (image.naturalWidth < 200 || image.naturalHeight < 200) { if (_ratio > 3) _ratio = 3; } else { if (_ratio > 2) _ratio = 2; } const width = image.naturalWidth * _ratio; const height = image.naturalHeight * _ratio; const x = image.naturalWidth * (ratio - _ratio) / 2; const y = image.naturalHeight * (ratio - _ratio) / 2; ratio = _ratio; image.style.width = width + 'px'; image.style.height = height + 'px'; marginLeft = marginLeft + x; marginTop = marginTop + y; image.style.marginLeft = marginLeft + 'px'; image.style.marginTop = marginTop + 'px'; } let marginLeft = 0, marginTop = 0; function touchMove(x, y) { if(isInit === false) return; marginLeft = marginLeft + x; marginTop = marginTop + y; image.style.marginLeft = marginLeft + 'px'; image.style.marginTop = marginTop + 'px'; } function toShow() { if(isInit === false) return; // canvas.toDataURL() 返回的是图片的base64字符串 // document.getElementById("img").src = dropCanvas.toDataURL(); // dropCanvas.toBlob((result) => { // // canvas 可以直接转换为blob // const file = blobToFile(result, "裁剪的图片"); // console.log(file); // }); dropCtx.clearRect(0, 0, 300, 300); // 这里可以使用canvas 替换 img 展示图片 缩放和移动时 使用context.drawImage 重绘即可 // 这里获取截图时 直接从这个canvas 截取即可 就不需要计算比例改变的问题了 // 注意这里是放到img标签中,缩放的是img这个壳子 // image 图像本身并没有变化 所以这里裁剪图片时要把坐标和宽高转换为缩放比例为1时的对应比例 // 比如 图像原始宽高 1000 * 1000 图片盒子宽高为 500*500 截取大小为300*300 截取图片中间位置 // 图像宽高 1000 * 1000 截取宽高为 300*300 x=350 y=350 ratio=1.0 marginLeft=marginTop=-250 dropLeft=dropTop=100 // 图像宽高为 500 * 500 截取宽高为 600*600 x=200 y=200 ratio=0.5 marginLeft=marginTop=0 dropLeft=dropTop=100 // 图像宽高为 1500 * 1500 截取宽高为 200*200 x=400 y=400 ratio=1.5 marginLeft=marginTop=-300 dropLeft=dropTop=100 // 盒子和裁剪框大小不用缩放 dropLeft,dropTop 初始化后大小是固定的 // 图像距离盒子顶部和底部的距离 我们直接获取即可 // 需要裁剪的坐标和宽高 计算如下 // 图片放大 需要截取的宽高越小 x和y坐标值也越小 // 感觉有点反人类,一般情况下可以使用双canvas 缩放时计算好坐标和宽高 使用drawImage重绘即可 最后我们截图时宽高不需要计算 一直都是300*300 不会为ratio改变而改变 // 坐标值直接计算即可 也不需要考虑ratio是多少 const x = (dropLeft - marginLeft) / ratio; const y = (dropTop - marginTop) / ratio; const width = 300 / ratio; const height = 300 / ratio; // drawImage 中间四个参数 处理裁剪问题 最后四个参数 图片的在canvas上的位置和宽高 dropCtx.drawImage(image, x, y, width, height, 0, 0, 300, 300); const blob = dataURLToBlob(dropCanvas.toDataURL()); const file = blobToFile(blob, "裁剪的图片"); console.log(file); } // DataURL 转 Blob function dataURLToBlob(dataUrl) { const arr = dataUrl.split(","); // base64 图片 开始位置 data:image/png;base64, 在`:`和`;`之间是文件的mimeType(也叫contenType) // match 匹配的值是一个数组 第一个是正则匹配的值 第二个是小括号内匹配的值 // 逗号之前是编码信息 逗号之后才是图片base64编码后的串 const mime = arr[0].match(/:(.*?);/)[1]; const bytes = atob(arr[1]); // 使用ArrayBuffer 说是可以处理 ascii 小于0的情况 const ab = new ArrayBuffer(bytes.length); const ia = new Uint8Array(ab); for (let i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i); } return new Blob([ab], {type: mime}); } // Blob 转文件 function blobToFile(blob, fileName) { return new File([blob], fileName, {type: blob.type}); } // 处理缩放时边界问题 // 首先要判断宽高 必须全部大于裁剪框宽高 // 然后在移动边界问题 function handleZoomBoundary() { if(isInit === false) return; if (ratio < 0.05) ratio = 0.05; if (ratio > 2) ratio = 2; let width = image.naturalWidth * ratio; let height = image.naturalHeight * ratio; if (width < 300 || height < 300) { if (width > height) { height = 300; ratio = 300 / image.naturalHeight; width = image.naturalWidth * ratio; } else { width = 300; ratio = 300 / image.naturalWidth; height = image.naturalHeight * ratio; } } image.style.width = width + 'px'; image.style.height = height + 'px'; } // 处理移动时的边界问题 function handleMoveBoundary() { if(isInit === false) return; const x = (dropWrapper.clientWidth - 300) / 2; const y = (dropWrapper.clientHeight - 300) / 2; const width = image.clientWidth; const height = image.clientHeight; if (marginLeft > x) { marginLeft = x; } if (marginTop > y) { marginTop = y; } if (marginLeft < 300 - width + x) { marginLeft = 300 - width + x; } if (marginTop < 300 - height + y) { marginTop = 300 - height + y; } console.log(width, height, marginLeft, marginTop); image.style.marginLeft = marginLeft + 'px'; image.style.marginTop = marginTop + 'px'; } // 处理居中 function handleCenter() { if(isInit === false) return; const width = image.clientWidth; const height = image.clientHeight; marginLeft = (300 - width) / 2 + dropLeft; marginTop = (300 - height) / 2 + dropTop; image.style.marginLeft = marginLeft + 'px'; image.style.marginTop = marginTop + 'px'; } let isTouching = false; let fingerNum = 0; // 几个手指触摸屏幕 const clientXs = []; const clientYs = []; document.querySelector(".wrapper").addEventListener('touchstart', (e) => { if(isInit === false) return; // e.stopPropagation(); e.preventDefault(); fingerNum = e.touches.length; for (let i = 0; i < fingerNum; i++) { clientXs[i] = e.touches[i].clientX; clientYs[i] = e.touches[i].clientY; } isTouching = true; console.log("开启", e.touches.length); }); document.querySelector(".wrapper").addEventListener('touchmove', (e) => { if(isInit === false) return; // e.stopPropagation(); e.preventDefault(); if (isTouching) { fingerNum = e.touches.length; if (fingerNum === 1) { // 一个手指表示需要移动图片的位置 const cx = e.touches[0].clientX; const cy = e.touches[0].clientY; const x = cx - clientXs[0]; const y = cy - clientYs[0]; touchMove(x, y); clientXs[0] = cx; clientYs[0] = cy; } else if (fingerNum === 2) { // 两个手指表示需要缩放图片 const cx1 = e.touches[0].clientX; const cy1 = e.touches[0].clientY; const cx2 = e.touches[1].clientX; const cy2 = e.touches[1].clientY; const diffX1 = cx1 - clientXs[0]; const diffX2 = cx2 - clientXs[1]; const diffY1 = cy1 - clientYs[0]; const diffY2 = cy2 - clientYs[1]; const pxRatio = 0.001; if (cx1 > cx2) { // sqrt 平方根 // pow 平方 const len = Math.sqrt(Math.pow(diffX1, 2) + Math.pow(diffY1, 2)) + Math.sqrt(Math.pow(diffX2, 2) + Math.pow(diffY2, 2)); if (diffX1 > 0 || diffX2 < 0) { // 放大 zoomIn(len * pxRatio); } if (diffX1 < 0 || diffX2 > 0) { // 缩小 zoomOut(len * pxRatio); } } else { const len = Math.sqrt(Math.pow(diffX1, 2) + Math.pow(diffY1, 2)) + Math.sqrt(Math.pow(diffX2, 2) + Math.pow(diffY2, 2)); if (diffX1 > 0 || diffX2 < 0) { // 缩小 zoomOut(len * pxRatio); } if (diffX1 < 0 || diffX2 > 0) { // 放大 zoomIn(len * pxRatio); } } clientXs[0] = cx1; clientYs[0] = cy1; clientXs[1] = cx2; clientYs[1] = cy2; } } }); document.querySelector(".wrapper").addEventListener('touchend', (e) => { if(isInit === false) return; // e.stopPropagation(); e.preventDefault(); // 触摸结束时获取不到结束点 这里根据图片最后的位置和宽高计算图片边界问题 isTouching = false; if (fingerNum === 1) { handleMoveBoundary(); } if (fingerNum === 2) { handleZoomBoundary(); handleMoveBoundary(); } console.log("关闭"); }); </script> </body> </html>
原文地址:https://www.cnblogs.com/rchao/p/15497767.html