UNPKG

@zhuxiaozy/clipimg

Version:
469 lines (435 loc) 16.1 kB
import Mouse from "./mouse.js"; interface ClipData { url: string; blob: Blob; }`` interface Config { el: HTMLDivElement; reviewCall?: (url: string) => void; } interface ImageCtx { x: number; y: number; width: number; height: number; n: number; img: HTMLImageElement; draw: () => void; } let mouse: Mouse; export default class ClipImg { private readonly el: HTMLDivElement; private readonly canvas: HTMLCanvasElement; private context: CanvasRenderingContext2D; private imgCtx: ImageCtx; private readonly controllerBox: HTMLDivElement; private controllerIsDown: boolean; private readonly reviewCall; private reviewSrc: string; constructor(config: Config) { this.reviewSrc = '' this.controllerIsDown = false; this.el = config.el; this.reviewCall = config.reviewCall; this.canvas = document.createElement("canvas"); this.controllerBox = document.createElement("div"); this.context = this.canvas.getContext("2d") as CanvasRenderingContext2D; this.imgCtx = { x: 0, y: 0, width: 0, height: 0, n: 0, img: document.createElement("img"), draw: () => { this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); this.context.drawImage( this.imgCtx.img, this.imgCtx.x, this.imgCtx.y, this.imgCtx.width, this.imgCtx.height ); this.getClipView(); }, }; mouse = new Mouse(); this.init(); } private init(): void { this.el.style.position = "relative"; this.el.appendChild(this.canvas); this.canvas.width = this.el.clientWidth; this.canvas.height = this.el.clientHeight; this.getEvent(); this.createControlBox(); this.createControlBoxImg() } private createControlBoxImg(): void { if (this.reviewSrc) { this.controllerBox.style.backgroundImage = `url('${this.reviewSrc}')`; this.controllerBox.style.backgroundSize = '100% 100%'; this.controllerBox.style.backgroundPosition = '0 0'; } } private getEvent(): void { let isDown = false; let lx: number; let ly: number; this.el.addEventListener("mousedown", (e: MouseEvent) => { if (this.controllerIsDown) { return; } const offsetX = e.pageX-this.el.getBoundingClientRect().x; const offsetY = e.pageY - this.el.getBoundingClientRect().y; lx = this.imgCtx.x - offsetX; ly = this.imgCtx.y - offsetY; isDown = true; }); this.el.addEventListener("mousemove", (e: MouseEvent) => { if (this.controllerIsDown) { return; } const offsetX = e.pageX-this.el.getBoundingClientRect().x; const offsetY = e.pageY - this.el.getBoundingClientRect().y; // 判断鼠标是否指向图片 if ( offsetX > this.imgCtx.x && offsetX < this.imgCtx.x + this.imgCtx.width && offsetY > this.imgCtx.y && offsetY < this.imgCtx.y + this.imgCtx.height ) { // 判断鼠标是否按下 if (isDown) { this.imgCtx.x = offsetX + lx; this.imgCtx.y = offsetY + ly; this.imgCtx.draw(); } } }); this.el.addEventListener("mouseleave", () => { isDown = false; }); this.el.addEventListener("mouseup", () => { isDown = false; }); const n = 8; mouse.scrollDown = () => { this.imgCtx.width += n; if (this.imgCtx.width <= this.canvas.width * 3) { this.imgCtx.height = this.imgCtx.width / this.imgCtx.n; this.imgCtx.x -= n / 2; this.imgCtx.y -= n / 2; } else { this.imgCtx.width = this.canvas.width * 3; this.imgCtx.height = this.imgCtx.width / this.imgCtx.n; } this.imgCtx.draw(); }; mouse.scrollUp = () => { this.imgCtx.width -= n; if (this.imgCtx.width >= 50) { this.imgCtx.height = this.imgCtx.width / this.imgCtx.n; this.imgCtx.x += n / 2; this.imgCtx.y += n / 2; } else { this.imgCtx.width = 50; this.imgCtx.height = this.imgCtx.width / this.imgCtx.n; } this.imgCtx.draw(); }; } /** * 写入图片 * @param file<File> 图片文件 */ public setImg(file: File): void { const img: HTMLImageElement = new Image(); img.src = URL.createObjectURL(file); img.onload = () => { const imgWidth = img.width; const imgHeight = img.height; const n = imgWidth / imgHeight; let width = this.canvas.width; let height = this.canvas.height; if (n > 1.7777777) { width = this.canvas.width; height = width / n; } else if (n < 1.7777777) { height = this.canvas.height; width = height * n; } const x = (this.canvas.width - width) / 2; const y = (this.canvas.height - height) / 2; this.imgCtx.x = x; this.imgCtx.y = y; this.imgCtx.width = width; this.imgCtx.height = height; this.imgCtx.n = n; this.imgCtx.img = img; this.imgCtx.draw(); }; } /** * 设置图片裁剪范围比例 * @param width * @param height */ public setSize(width = 160, height = 90): void { this.controllerBox.style.width = width + "px"; this.controllerBox.style.height = height + "px"; } private createControlBox() { this.controllerBox.style.cssText = ` position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); border: 1px solid #5c5c5c; `; this.setSize(160, 90); const pointBtn = [ this.createControlBtn(8, 8, "lt"), this.createControlBtn(8, 8, "rt"), this.createControlBtn(8, 8, "lb"), this.createControlBtn(8, 8, "rb"), ]; pointBtn.forEach((btn: HTMLDivElement) => { this.controllerBox.appendChild(btn); }); this.el.appendChild(this.controllerBox); } private createControlBtn( width: number, height: number, type: string ): HTMLDivElement { const div = document.createElement("div"); div.style.cssText = ` width: ${width}px; height: ${height}px; position: absolute; cursor: pointer; background: #cccccc; `; div.setAttribute("type", type); if (type === "lt" || type === "rt") { div.style.top = -height / 2 + "px"; } if (type === "lt" || type === "lb") { div.style.left = -width / 2 + "px"; } if (type === "lb" || type === "rb") { div.style.bottom = -height / 2 + "px"; } if (type === "rt" || type === "rb") { div.style.right = -width / 2 + "px"; } let divType = ""; let n = 0; div.addEventListener("mousedown", () => { n = this.controllerBox.offsetWidth / this.controllerBox.offsetHeight; divType = div.getAttribute("type") as string; this.controllerIsDown = true; }); this.el.addEventListener("mouseup", () => { divType = ""; this.controllerIsDown = false; }); this.el.addEventListener("mouseleave", () => { divType = ""; this.controllerIsDown = false; }); this.el.addEventListener( "mousemove", (e: MouseEvent) => { if (this.controllerIsDown) { const parent: HTMLDivElement = div.parentNode as HTMLDivElement; // const x = e.pageX - this.clientX(this.el); const x = e.pageX-this.el.getBoundingClientRect().x; // console.log(x, e.pageX-this.el.getBoundingClientRect().x) if (divType === "lt" || divType === "lb") { let width = this.canvas.width - x * 2; width = width>this.canvas.width?this.canvas.width:width width = width<100?100:width if (width/n>this.canvas.height) { parent.style.width = this.canvas.height*n + "px"; parent.style.height = this.canvas.height + "px"; return } parent.style.width = width + "px"; parent.style.height = width / n + "px"; this.getClipView(); } if (divType === "rt" || divType === "rb") { let width = this.canvas.width - (this.canvas.width - x) * 2; width = width>this.canvas.width?this.canvas.width:width; width = width<100?100:width if (width/n>this.canvas.height) { parent.style.width = this.canvas.height*n + "px"; parent.style.height = this.canvas.height + "px"; return } parent.style.width = width + "px"; parent.style.height = width / n + "px"; this.getClipView(); } } }, true ); return div; } private computeSize(width:number) { } private getClipView() { if(!this.context) { return } const opt = this.controllerBox.getBoundingClientRect(); const x = this.controllerBox.offsetLeft - opt.width / 2; const y = this.controllerBox.offsetTop - opt.height / 2; const width = opt.width; const height = opt.height; const data = this.context.getImageData(x, y, width, height); const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; ctx.putImageData(data, 0, 0); this.reviewSrc = canvas.toDataURL('image/jpeg') this.createControlBoxImg() if (this.reviewCall) { this.reviewCall(this.reviewSrc); } // canvas.toBlob((blob) => { // const url = URL.createObjectURL(blob); // this.reviewSrc = url // this.createControlBoxImg() // if (this.reviewCall) { // this.reviewCall(url); // } // }); } /** * 获取裁剪图片 * @param size<Number> */ public getClipData(size = 4096): Promise<ClipData> { return new Promise((resolve) => { document.body.appendChild(this.imgCtx.img); const opt = this.controllerBox.getBoundingClientRect(); const x = this.controllerBox.offsetLeft - opt.width / 2; const y = this.controllerBox.offsetTop - opt.height / 2; const width = opt.width; const height = opt.height; const n = this.imgCtx.img.offsetWidth / this.imgCtx.width; const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; canvas.width = this.imgCtx.img.offsetWidth; canvas.height = this.imgCtx.img.offsetHeight; document.body.removeChild(this.imgCtx.img); ctx.drawImage(this.imgCtx.img, 0, 0, canvas.width, canvas.height); const data = ctx.getImageData( (x - this.imgCtx.x) * n, (y - this.imgCtx.y) * n, width * n, height * n ); const canvas2 = document.createElement("canvas"); const ctx2 = canvas2.getContext("2d") as CanvasRenderingContext2D; canvas2.width = width * n; canvas2.height = height * n; ctx2.putImageData(data, 0, 0); canvas2.toBlob(async (blob) => { let blobs = await this.compress(blob as Blob); //判断图片是否小于规定大小,没有继续压缩 while (blobs.size / 1024 > size) { blobs = await this.compress(blobs); } const url = URL.createObjectURL(blobs); resolve({ url: url, blob: blobs, }); }); }); } /** * 压缩图片 * @param blob<Blob> * @private */ private compress(blob: Blob): Promise<Blob> { return new Promise<Blob>((resolve) => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; const img = new Image(); img.style.cssText = ` position:absolute; top:-9999999px; left:-99999999; `; document.body.appendChild(img); img.onload = () => { canvas.width = img.width * 0.9; canvas.height = img.height * 0.9; document.body.removeChild(img); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob((blobs) => { resolve(blobs as Blob); }, "image/jpeg"); }; img.src = URL.createObjectURL(blob); }); } clientX(el: HTMLElement): number { return this.pageX(el) - this.scrollX(); } clientY(el: HTMLElement): number { return this.pageY(el) - this.scrollY(); } pageX(el: HTMLElement): number { let parent = el; let x = 0; while (parent) { x += parent.offsetLeft; parent = parent.offsetParent as HTMLElement; } return x; } pageY(el: HTMLElement): number { let parent = el; let y = 0; while (parent) { y += parent.offsetTop; parent = parent.offsetParent as HTMLElement; } return y; } scrollX(elOrWindow?: Element): number { let x = 0; // if (this.isWindow(elOrWindow)) { // const win = elOrWindow as Window; // x = // win.scrollX || // win.pageXOffset || // win.document.documentElement.scrollLeft; // } else if (this.isElement(elOrWindow)) { // x = (elOrWindow as Element).scrollLeft; // } if (elOrWindow) { x = (elOrWindow as Element).scrollLeft; } return x; } scrollY(elOrWindow?: Element): number { let y = 0; if (elOrWindow) { y = (elOrWindow as Element).scrollTop; } return y; } } // @ts-ignore window['ClipImg'] = ClipImg