|
| 1 | +# Canvas API 截取图片的一部分 |
| 2 | + |
| 3 | +::: info |
| 4 | +自由截图,可结合html2canvas库使用 |
| 5 | +::: |
| 6 | + |
| 7 | +```js |
| 8 | +// 加载图片 |
| 9 | +let img = new Image() |
| 10 | +img.src = 'image_url.jpg' // 需要截取的图片的URL |
| 11 | + |
| 12 | +img.onload = function () { |
| 13 | + // 创建canvas |
| 14 | + let canvas = document.createElement('canvas') |
| 15 | + let context = canvas.getContext('2d') |
| 16 | + |
| 17 | + // 设定需要截取的区域(这里为图片的左上角100x100像素的区域) |
| 18 | + let crop_x = 0 |
| 19 | + let crop_y = 0 |
| 20 | + let crop_width = 100 |
| 21 | + let crop_height = 100 |
| 22 | + |
| 23 | + // 设置canvas的尺寸 |
| 24 | + canvas.width = crop_width |
| 25 | + canvas.height = crop_height |
| 26 | + |
| 27 | + // 在canvas上绘制选定的区域 |
| 28 | + context.drawImage(img, crop_x, crop_y, crop_width, crop_height, 0, 0, crop_width, crop_height) |
| 29 | + |
| 30 | + // 将canvas转换为图片,并下载 |
| 31 | + let imageUrl = canvas.toDataURL('image/png') |
| 32 | + let link = document.createElement('a') |
| 33 | + link.href = imageUrl |
| 34 | + link.download = 'screenshot.png' |
| 35 | + link.click() |
| 36 | + link.remove() |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +```js |
| 41 | +const clip = () => { |
| 42 | + const clipAreaWrap = useRef(null) // 截图区域dom |
| 43 | + const clipCanvas = useRef(null) // 用于截图的的canvas,以及截图开始生成截图效果(背景置灰) |
| 44 | + const drawCanvas = useRef(null) // 把图片绘制到canvas上方便 用于生成截取图片的base64数据 |
| 45 | + const [clipImgData, setClipImgData] = useState('') |
| 46 | + |
| 47 | + const init = (wrap) => { |
| 48 | + if (!wrap) return |
| 49 | + clipAreaWrap.current = wrap |
| 50 | + clipCanvas.current = document.createElement('canvas') |
| 51 | + drawCanvas.current = document.createElement('canvas') |
| 52 | + clipCanvas.current.style = |
| 53 | + 'width:100%;height:100%;z-index: 2;position: absolute;left: 0;top: 0;' |
| 54 | + drawCanvas.current.style = |
| 55 | + 'width:100%;height:100%;z-index: 1;position: absolute;left: 0;top: 0;' |
| 56 | + |
| 57 | + clipAreaWrap.current.appendChild(clipCanvas.current) |
| 58 | + clipAreaWrap.current.appendChild(drawCanvas.current) |
| 59 | + } |
| 60 | + // 截图 |
| 61 | + const cut = (souceImg: string) => { |
| 62 | + const drawCanvasCtx = drawCanvas.current.getContext('2d') |
| 63 | + const clipCanvasCtx = clipCanvas.current.getContext('2d') |
| 64 | + |
| 65 | + const wrapWidth = clipAreaWrap.current.clientWidth |
| 66 | + const wrapHeight = clipAreaWrap.current.clientHeight |
| 67 | + clipCanvas.current.width = wrapWidth |
| 68 | + clipCanvas.current.height = wrapHeight |
| 69 | + drawCanvas.current.width = wrapWidth |
| 70 | + drawCanvas.current.height = wrapHeight |
| 71 | + |
| 72 | + // 设置截图时灰色背景 |
| 73 | + clipCanvasCtx.fillStyle = 'rgba(0,0,0,0.6)' |
| 74 | + clipCanvasCtx.strokeStyle = 'rgba(0,143,255,1)' |
| 75 | + |
| 76 | + // 生成一个截取区域的img 然后把它作为canvas的第一个参数 |
| 77 | + const clipImg = document.createElement('img') |
| 78 | + clipImg.classList.add('img_anonymous') |
| 79 | + clipImg.crossOrigin = 'anonymous' |
| 80 | + clipImg.src = souceImg |
| 81 | + |
| 82 | + // Q: 这里为什么需要append到clipAreaWrap里 |
| 83 | + // A: 因为直接clipImg.src的引入是没有css样式的(主要是宽高)如果不append直接进行drawCanvasCtx.drawImage, |
| 84 | + // 那其实画的是原始大小的clipImg |
| 85 | + clipAreaWrap.current.appendChild(clipImg) |
| 86 | + |
| 87 | + // 绘制截图区域 |
| 88 | + clipImg.onload = () => { |
| 89 | + // x,y -> 计算从drawCanvasCtx的的哪一x,y坐标点进行绘制 |
| 90 | + const x = Math.floor((wrapWidth - clipImg.width) / 2) |
| 91 | + const y = Math.floor((wrapHeight - clipImg.height) / 2) |
| 92 | + // Q: 为什么这里要用克隆节点的宽高 |
| 93 | + // A: 因为clipImg的宽高是在dom中已经被css修改过的宽高(长/宽)了,而非该图片的真实长和宽 |
| 94 | + // 用这个宽高在drawCanvasCtx的绘图只会绘制clipImg的小部分内容(因为假宽高比真宽高小),看起来就像是被放大了 |
| 95 | + const clipImgCopy = clipImg.cloneNode() |
| 96 | + drawCanvasCtx.drawImage( |
| 97 | + clipImg, |
| 98 | + 0, |
| 99 | + 0, |
| 100 | + clipImgCopy.width, |
| 101 | + clipImgCopy.height, |
| 102 | + x, |
| 103 | + y, |
| 104 | + clipImg.width, |
| 105 | + clipImg.height |
| 106 | + ) |
| 107 | + } |
| 108 | + |
| 109 | + let start = null |
| 110 | + |
| 111 | + // 获取截图开始的点 |
| 112 | + clipCanvas.current.onmousedown = function (e) { |
| 113 | + start = { |
| 114 | + x: e.offsetX, |
| 115 | + y: e.offsetY |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + // 绘制截图区域效果 |
| 120 | + clipCanvas.current.onmousemove = function (e) { |
| 121 | + if (start) { |
| 122 | + fill( |
| 123 | + clipCanvasCtx, |
| 124 | + wrapWidth, |
| 125 | + wrapHeight, |
| 126 | + start.x, |
| 127 | + start.y, |
| 128 | + e.offsetX - start.x, |
| 129 | + e.offsetY - start.y |
| 130 | + ) |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + // 截图完毕,获取截图图片数据 |
| 135 | + document.addEventListener('mouseup', function (e) { |
| 136 | + if (start) { |
| 137 | + var url = getClipPicUrl( |
| 138 | + { |
| 139 | + x: start.x, |
| 140 | + y: start.y, |
| 141 | + w: e.offsetX - start.x, |
| 142 | + h: e.offsetY - start.y |
| 143 | + }, |
| 144 | + drawCanvasCtx |
| 145 | + ) |
| 146 | + start = null |
| 147 | + //生成base64格式的图 |
| 148 | + setClipImgData(url) |
| 149 | + } |
| 150 | + }) |
| 151 | + } |
| 152 | + |
| 153 | + const cancelCut = () => { |
| 154 | + clipCanvas.current.width = clipAreaWrap.current.clientWidth |
| 155 | + clipCanvas.current.height = clipAreaWrap.current.clientHeight |
| 156 | + drawCanvas.current.width = clipAreaWrap.current.clientWidth |
| 157 | + drawCanvas.current.height = clipAreaWrap.current.clientHeight |
| 158 | + const drawCanvasCtx = drawCanvas.current.getContext('2d') |
| 159 | + const clipCanvasCtx = clipCanvas.current.getContext('2d') |
| 160 | + drawCanvasCtx.clearRect(0, 0, drawCanvas.current.clientWidth, drawCanvas.current.clientHeight) |
| 161 | + clipCanvasCtx.clearRect(0, 0, clipCanvas.current.clientWidth, clipCanvas.current.clientHeight) |
| 162 | + //移除鼠标事件 |
| 163 | + clipCanvas.current.onmousedown = null |
| 164 | + clipCanvas.current.onmousemove = null |
| 165 | + } |
| 166 | + |
| 167 | + const getClipPicUrl = (area, drawCanvasCtx) => { |
| 168 | + const canvas = document.createElement('canvas') |
| 169 | + const context = canvas.getContext('2d') |
| 170 | + const data = drawCanvasCtx.getImageData(area.x, area.y, area.w, area.h) |
| 171 | + canvas.width = area.w |
| 172 | + canvas.height = area.h |
| 173 | + context.putImageData(data, 0, 0) |
| 174 | + return canvas.toDataURL('image/png', 1) |
| 175 | + } |
| 176 | + |
| 177 | + // 绘制出截图的效果 |
| 178 | + const fill = (context, ctxWidth, ctxHeight, x, y, w, h) => { |
| 179 | + context.clearRect(0, 0, ctxWidth, ctxHeight) |
| 180 | + context.beginPath() |
| 181 | + //遮罩层 |
| 182 | + context.globalCompositeOperation = 'source-over' |
| 183 | + context.fillRect(0, 0, ctxWidth, ctxHeight) |
| 184 | + //画框 |
| 185 | + context.globalCompositeOperation = 'destination-out' |
| 186 | + context.fillRect(x, y, w, h) |
| 187 | + //描边 |
| 188 | + context.globalCompositeOperation = 'source-over' |
| 189 | + context.moveTo(x, y) |
| 190 | + context.lineTo(x + w, y) |
| 191 | + context.lineTo(x + w, y + h) |
| 192 | + context.lineTo(x, y + h) |
| 193 | + context.lineTo(x, y) |
| 194 | + // context.stroke() |
| 195 | + context.closePath() |
| 196 | + } |
| 197 | + return { init, cut, cancelCut, clipImgData } |
| 198 | +} |
| 199 | +``` |
0 commit comments