稍微改改就能使用了,这里预览直接在裁剪框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>