create-puzzle
Version:
在浏览器端生成滑块验证码的拼图和背景图。
325 lines (319 loc) • 13 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var utilHelpers = require('util-helpers');
var ut2 = require('ut2');
var mathPI = Math.PI;
// 拼图点
exports.Point = void 0;
(function (Point) {
Point[Point["None"] = 0] = "None";
Point[Point["Outer"] = 1] = "Outer";
Point[Point["Inner"] = 2] = "Inner"; // 内部
})(exports.Point || (exports.Point = {}));
var pointArray = [exports.Point.None, exports.Point.Outer, exports.Point.Inner];
// 随机选择数组中的某一项
function pick(arr) {
var len = arr.length;
var randomIndex = ut2.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 === exports.Point.Outer && points.bottom === exports.Point.Outer) {
points[pick(verticalDirs)] = exports.Point.Inner;
}
else if (points.top !== exports.Point.Outer && points.bottom !== exports.Point.Outer) {
points[pick(verticalDirs)] = exports.Point.Outer;
}
if (points.left === exports.Point.Outer && points.right === exports.Point.Outer) {
points[pick(horizontalDirs)] = exports.Point.Inner;
}
else if (points.left !== exports.Point.Outer && points.right !== exports.Point.Outer) {
points[pick(horizontalDirs)] = exports.Point.Outer;
}
if (pointNum) {
var inners_1 = [];
var nones_1 = [];
pointsKeys.forEach(function (item) {
if (points[item] === exports.Point.Inner) {
inners_1.push(item);
}
else if (points[item] === exports.Point.None) {
nones_1.push(item);
}
});
if (pointNum === 2) {
inners_1.forEach(function (item) { return (points[item] = exports.Point.None); });
}
else if (pointNum === 3) {
if (inners_1.length === 0) {
points[pick(nones_1)] = exports.Point.Inner;
}
else if (inners_1.length === 2) {
points[pick(inners_1)] = exports.Point.None;
}
}
else if (pointNum == 4) {
nones_1.forEach(function (item) { return (points[item] = exports.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 === exports.Point.Outer) {
rect.x += c2r;
}
if (points.top === exports.Point.Outer) {
rect.y += c2r;
}
// draw start
ctx.beginPath();
ctx.lineWidth = 2;
// top
ctx.moveTo(rect.x, rect.y);
if (points.top !== exports.Point.None) {
ctx.lineTo(rect.x + w1_2 - l1_2, rect.y);
if (points.top === exports.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 !== exports.Point.None) {
ctx.lineTo(rect.x + rect.w, rect.y + h1_2 - l1_2);
if (points.right === exports.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 !== exports.Point.None) {
ctx.lineTo(rect.x + w1_2 + l1_2, rect.y + rect.h);
if (points.bottom === exports.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 !== exports.Point.None) {
ctx.lineTo(rect.x, rect.y + h1_2 + l1_2);
if (points.left === exports.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 utilHelpers.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 (!ut2.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 utilHelpers.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' ? ut2.randomInt(width, maxOffsetX) : outX || 0;
var y = typeof outY === 'undefined' ? ut2.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);
});
}
exports.clearCache = clearCache;
exports.createPuzzle = createPuzzle;
exports.default = createPuzzle;
exports.drawPuzzle = drawPuzzle;
exports.getRandomPoints = getRandomPoints;