UNPKG

create-puzzle

Version:

在浏览器端生成滑块验证码的拼图和背景图。

317 lines (313 loc) 12.6 kB
import { AsyncMemo, loadImageWithBlob } from 'util-helpers'; import { randomInt, isObject } from 'ut2'; var mathPI = Math.PI; // 拼图点 var Point; (function (Point) { Point[Point["None"] = 0] = "None"; Point[Point["Outer"] = 1] = "Outer"; Point[Point["Inner"] = 2] = "Inner"; // 内部 })(Point || (Point = {})); var pointArray = [Point.None, Point.Outer, Point.Inner]; // 随机选择数组中的某一项 function pick(arr) { var len = arr.length; var randomIndex = randomInt(0, len - 1); return arr[randomIndex]; } // 获取随机拼图点 function getRandomPoints(pointNum) { var points = { top: pick(pointArray), right: pick(pointArray), bottom: pick(pointArray), left: pick(pointArray) }; var pointsKeys = Object.keys(points); var verticalDirs = ['top', 'bottom']; var horizontalDirs = ['left', 'right']; // 保证上下 和 左右 都必须有一个外部的拼图点 if (points.top === Point.Outer && points.bottom === Point.Outer) { points[pick(verticalDirs)] = Point.Inner; } else if (points.top !== Point.Outer && points.bottom !== Point.Outer) { points[pick(verticalDirs)] = Point.Outer; } if (points.left === Point.Outer && points.right === Point.Outer) { points[pick(horizontalDirs)] = Point.Inner; } else if (points.left !== Point.Outer && points.right !== Point.Outer) { points[pick(horizontalDirs)] = Point.Outer; } if (pointNum) { var inners_1 = []; var nones_1 = []; pointsKeys.forEach(function (item) { if (points[item] === Point.Inner) { inners_1.push(item); } else if (points[item] === Point.None) { nones_1.push(item); } }); if (pointNum === 2) { inners_1.forEach(function (item) { return (points[item] = Point.None); }); } else if (pointNum === 3) { if (inners_1.length === 0) { points[pick(nones_1)] = Point.Inner; } else if (inners_1.length === 2) { points[pick(inners_1)] = Point.None; } } else if (pointNum == 4) { nones_1.forEach(function (item) { return (points[item] = Point.Inner); }); } } return points; } // 画拼图 function drawPuzzle(ctx, options) { if (options === void 0) { options = {}; } var _a = options.x, x = _a === void 0 ? 0 : _a, _b = options.y, y = _b === void 0 ? 0 : _b, _c = options.w, w = _c === void 0 ? 60 : _c, _d = options.h, h = _d === void 0 ? 60 : _d, _e = options.needClosePath, needClosePath = _e === void 0 ? true : _e; var points = options.points, _f = options.margin, margin = _f === void 0 ? 0 : _f; margin = margin <= 0 ? 0 : margin; if (typeof points === 'number' || !points) { points = getRandomPoints(points); } var r = (Math.min(w, h) - margin * 2) * 0.15; // 适合拼图点的比例 0.15 var l = Math.hypot(r, r); // 斜边长度 var l1_2 = l / 2; // 斜边长度一半,45度角直角三角形,邻边相等 var c2r = r + l1_2; // 圆直径 var rect = { x: x + margin, y: y + margin, w: w - c2r - margin * 2, h: h - c2r - margin * 2 }; var w1_2 = rect.w / 2; // 矩形一半宽度 var h1_2 = rect.h / 2; // 矩形一半高度 if (points.left === Point.Outer) { rect.x += c2r; } if (points.top === Point.Outer) { rect.y += c2r; } // draw start ctx.beginPath(); ctx.lineWidth = 2; // top ctx.moveTo(rect.x, rect.y); if (points.top !== Point.None) { ctx.lineTo(rect.x + w1_2 - l1_2, rect.y); if (points.top === Point.Inner) { ctx.arc(rect.x + w1_2, rect.y + l1_2, r, 1.25 * mathPI, 1.75 * mathPI, true); } else { ctx.arc(rect.x + w1_2, rect.y - l1_2, r, 0.75 * mathPI, 0.25 * mathPI); } } ctx.lineTo(rect.x + rect.w, rect.y); // right if (points.right !== Point.None) { ctx.lineTo(rect.x + rect.w, rect.y + h1_2 - l1_2); if (points.right === Point.Inner) { ctx.arc(rect.x + rect.w - l1_2, rect.y + h1_2, r, 1.75 * mathPI, 0.25 * mathPI, true); } else { ctx.arc(rect.x + rect.w + l1_2, rect.y + h1_2, r, 1.25 * mathPI, 0.75 * mathPI); } } ctx.lineTo(rect.x + rect.w, rect.y + rect.h); // bottom if (points.bottom !== Point.None) { ctx.lineTo(rect.x + w1_2 + l1_2, rect.y + rect.h); if (points.bottom === Point.Inner) { ctx.arc(rect.x + w1_2, rect.y + rect.h - l1_2, r, 0.25 * mathPI, 0.75 * mathPI, true); } else { ctx.arc(rect.x + w1_2, rect.y + rect.h + l1_2, r, 1.75 * mathPI, 1.25 * mathPI); } } ctx.lineTo(rect.x, rect.y + rect.h); // left if (points.left !== Point.None) { ctx.lineTo(rect.x, rect.y + h1_2 + l1_2); if (points.left === Point.Inner) { ctx.arc(rect.x + l1_2, rect.y + h1_2, r, 0.75 * mathPI, 1.25 * mathPI, true); } else { ctx.arc(rect.x - l1_2, rect.y + h1_2, r, 0.25 * mathPI, 1.75 * mathPI); } } ctx.lineTo(rect.x, rect.y); ctx.stroke(); if (needClosePath) { ctx.closePath(); } // ctx.fillStyle = "red"; // ctx.fill(); // ctx.strokeRect(x, y, w, h); } function canvasToImage(canvas, formatBlob, type, quality) { return new Promise(function (resolve) { if (formatBlob) { canvas.toBlob(function (blob) { if (blob) { resolve(URL.createObjectURL(blob)); } else { resolve(canvas.toDataURL(type, quality)); } }, type, quality); } else { resolve(canvas.toDataURL(type, quality)); } }); } var asyncMemo = new AsyncMemo({ max: 5, maxStrategy: 'replaced' }); asyncMemo.cache.on('del', function (k, v) { try { if (v.image.src) { URL.revokeObjectURL(v.image.src); } // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { /* empty */ } }); function clearCache(key) { if (key) { asyncMemo.cache.del(key); } else { asyncMemo.cache.clear(); } } var wm = new WeakMap(); var getCacheKey = function (obj) { if (!isObject(obj)) { return String(obj); } if (!wm.get(obj)) { var key = '_' + Date.now(); wm.set(obj, key); } return wm.get(obj); }; var MimeType = { jpeg: 'image/jpeg', png: 'image/png' }; // 缓存之前的 blob url var previousBlobUrlCache = []; function revokeBlobUrls(blobUrls) { blobUrls.forEach(function (item) { URL.revokeObjectURL(item); }); } // 创建拼图和背景图 function createPuzzle(imgUrl, options) { if (options === void 0) { options = {}; } var _a = options.borderWidth, borderWidth = _a === void 0 ? 2 : _a, _b = options.borderColor, borderColor = _b === void 0 ? 'rgba(255,255,255,0.7)' : _b, _c = options.fillColor, fillColor = _c === void 0 ? 'rgba(255,255,255,0.7)' : _c, outPoints = options.points, _d = options.width, width = _d === void 0 ? 60 : _d, _e = options.height, height = _e === void 0 ? 60 : _e, outX = options.x, outY = options.y, _f = options.margin, margin = _f === void 0 ? 2 : _f, _g = options.equalHeight, equalHeight = _g === void 0 ? true : _g, imageWidth = options.imageWidth, imageHeight = options.imageHeight, outBgWidth = options.bgWidth, outBgHeight = options.bgHeight, _h = options.bgOffset, outBgOffset = _h === void 0 ? [0, 0] : _h, _j = options.bgImageType, bgImageType = _j === void 0 ? MimeType.jpeg : _j, _k = options.quality, quality = _k === void 0 ? 0.8 : _k, _l = options.format, format = _l === void 0 ? 'dataURL' : _l, _m = options.cacheImage, cacheImage = _m === void 0 ? true : _m, _o = options.autoRevokePreviousBlobUrl, autoRevokePreviousBlobUrl = _o === void 0 ? true : _o, ajaxOptions = options.ajaxOptions; return new Promise(function (resolve, reject) { var bgCanvas = document.createElement('canvas'); var puzzleCanvas = document.createElement('canvas'); var bgCtx = bgCanvas.getContext('2d'); var puzzleCtx = puzzleCanvas.getContext('2d'); var cacheKey = cacheImage ? getCacheKey(imgUrl) : undefined; asyncMemo .run(function () { return loadImageWithBlob(imgUrl, ajaxOptions); }, cacheKey) .then(function (_a) { var img = _a.image; if (imageWidth) { img.width = imageWidth; } if (imageHeight) { img.height = imageHeight; } var bgWidth = typeof outBgWidth === 'number' && outBgWidth > 0 ? outBgWidth > width ? outBgWidth : width : img.width; var bgHeight = typeof outBgHeight === 'number' && outBgHeight > 0 ? outBgHeight > height ? outBgHeight : height : img.height; bgCanvas.width = bgWidth; bgCanvas.height = bgHeight; var maxOffsetX = bgWidth - width; var maxOffsetY = bgHeight - height; var x = typeof outX === 'undefined' ? randomInt(width, maxOffsetX) : outX || 0; var y = typeof outY === 'undefined' ? randomInt(0, maxOffsetY) : outY || 0; if (x < 0) { x = 0; } else if (x > maxOffsetX) { x = maxOffsetX; } if (y < 0) { y = 0; } else if (y > maxOffsetY) { y = maxOffsetY; } var points = typeof outPoints === 'number' || !outPoints ? getRandomPoints(outPoints) : outPoints; var bgOffset = typeof outBgOffset === 'function' ? outBgOffset(img.width, img.height) : outBgOffset; // 背景图 bgCtx.strokeStyle = borderColor; bgCtx.lineWidth = borderWidth; bgCtx.fillStyle = fillColor; drawPuzzle(bgCtx, { x: x, y: y, w: width, h: height, points: points, margin: margin }); bgCtx.fillStyle = fillColor; bgCtx.fill(); bgCtx.globalCompositeOperation = 'destination-over'; bgCtx.drawImage(img, bgOffset[0], bgOffset[1], img.width, img.height); puzzleCanvas.width = bgWidth; puzzleCanvas.height = bgHeight; // 拼图 puzzleCtx.strokeStyle = borderColor; puzzleCtx.lineWidth = borderWidth; drawPuzzle(puzzleCtx, { x: x, y: y, w: width, h: height, points: points, margin: margin }); puzzleCtx.globalCompositeOperation = 'destination-over'; puzzleCtx.clip(); puzzleCtx.drawImage(img, bgOffset[0], bgOffset[1], img.width, img.height); // restore image var imgData = puzzleCtx.getImageData(x, y, width, height); puzzleCtx.clearRect(0, 0, bgWidth, bgHeight); puzzleCanvas.width = width; puzzleCanvas.height = equalHeight ? bgHeight : height; puzzleCtx.putImageData(imgData, 0, equalHeight ? y : 0); var formatBlob = format === 'blob'; var puzzlePromise = canvasToImage(puzzleCanvas, formatBlob, MimeType.png, quality); var bgPromise = canvasToImage(bgCanvas, formatBlob, bgImageType, quality); Promise.all([puzzlePromise, bgPromise]) .then(function (_a) { var puzzleUrl = _a[0], bgUrl = _a[1]; if (autoRevokePreviousBlobUrl) { if (previousBlobUrlCache.length) { revokeBlobUrls(previousBlobUrlCache); previousBlobUrlCache.length = 0; } if (formatBlob) { previousBlobUrlCache.push(bgUrl, puzzleUrl); } } resolve({ puzzleUrl: puzzleUrl, bgUrl: bgUrl, x: x, y: equalHeight ? 0 : y }); }) .catch(reject); }) .catch(reject); }); } export { Point, clearCache, createPuzzle, createPuzzle as default, drawPuzzle, getRandomPoints };