This commit is contained in:
iqudoo
2026-05-12 19:59:42 +08:00
parent bc025f7f74
commit c56c17589a
4 changed files with 117 additions and 10 deletions

10
dist/index.d.ts vendored
View File

@@ -54,7 +54,7 @@ interface ScanConfigOptions {
*/ */
webScanCanvasEnabled?: boolean, webScanCanvasEnabled?: boolean,
/** /**
* 网页扫码canvas样式默认position: fixed; width: 300px; height: 300px; top: 0; left: 0; z-index: 9999; * 网页扫码canvas样式默认PC 为左上角固定 300×300移动端为 min(视口宽, 视口高) 的正方形,固定在左上角;z-index: 9999
*/ */
webScanCanvasStyle?: string, webScanCanvasStyle?: string,
/** /**
@@ -69,6 +69,14 @@ interface ScanConfigOptions {
* 网页扫码关闭按钮的 class * 网页扫码关闭按钮的 class
*/ */
webScanCloseButtonClass?: string, webScanCloseButtonClass?: string,
/**
* Canvas 开启且支持图片识别时关闭按钮下方的「选图」按钮样式fixed 定位基准由 SDK 计算,可与关闭按钮一致覆盖)
*/
webScanPickImageButtonStyle?: string,
/**
* 网页扫码选图按钮的 class
*/
webScanPickImageButtonClass?: string,
/** /**
* 网页扫码类型,默认支持二维码和条码 * 网页扫码类型,默认支持二维码和条码
*/ */

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,16 @@ const scanWeb = {
const DEFAULT_SCAN_BEEP_AUDIO = scanBeepAudio; const DEFAULT_SCAN_BEEP_AUDIO = scanBeepAudio;
const ZXING_READER_WASM_PATH = "lib/reader.wasm"; const ZXING_READER_WASM_PATH = "lib/reader.wasm";
const WEBSCAN_CLOSE_BUTTON_ID = "__webscan_close_button__"; const WEBSCAN_CLOSE_BUTTON_ID = "__webscan_close_button__";
const WEBSCAN_CLOSE_ICON_SVG = "<svg viewBox=\"0 0 1024 1024\" xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M139.445 1002.491L512.23 629.716l372.775 372.775c28.672 28.672 78.843 28.672 107.52 0 28.703-28.672 28.703-78.842 0-107.52L619.75 522.187l372.775-372.795c28.703-28.672 28.703-78.853 0-107.52-28.677-28.672-78.848-28.672-107.52 0L512.23 414.636 139.445 41.876c-28.677-28.672-78.848-28.672-107.54 0C17.963 55.813 10.95 74.828 10.55 93.972v3.318c0.4 19.148 7.413 38.164 21.355 52.106L404.69 522.19 31.905 894.971c-13.942 13.917-20.956 32.973-21.355 52.096v3.292c0.4 19.16 7.413 38.175 21.355 52.137 28.692 28.662 78.868 28.662 107.54-0.005z\" fill=\"currentColor\"/></svg>"; const WEBSCAN_PICK_IMAGE_BUTTON_ID = "__webscan_pick_image_button__";
const DEFAULT_WEBSCAN_CLOSE_BUTTON_STYLE = "width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%; background: rgba(0, 0, 0, 0.55); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10000;"; const WEBSCAN_CLOSE_ICON_SVG = "<svg viewBox=\"0 0 1024 1024\" xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M566.97558594 521.09667969L856.8828125 231.18945312c14.63378906-14.63378906 14.63378906-38.75976563 0-53.39355468l-1.58203125-1.58203125c-14.63378906-14.63378906-38.75976563-14.63378906-53.39355469 0L512 466.51660156 222.09277344 176.21386719c-14.63378906-14.63378906-38.75976563-14.63378906-53.39355469 0l-1.58203125 1.58203125c-15.02929688 14.63378906-15.02929688 38.75976563 0 53.39355469l289.90722656 289.90722656L167.1171875 811.00390625c-14.63378906 14.63378906-14.63378906 38.75976563 0 53.39355469l1.58203125 1.58203125c14.63378906 14.63378906 38.75976563 14.63378906 53.39355469 0L512 576.07226563 801.90722656 865.97949219c14.63378906 14.63378906 38.75976563 14.63378906 53.39355469 0l1.58203125-1.58203125c14.63378906-14.63378906 14.63378906-38.75976563 0-53.39355469L566.97558594 521.09667969z\" fill=\"currentColor\"/></svg>";
const WEBSCAN_PICK_IMAGE_ICON_SVG = "<svg viewBox=\"0 0 1024 1024\" xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M896 160H128v704h768zm-704 64h640v423.68L706.08 496l-157.12 157.12L352 358.24l-160 240zm0 576v-86.24l160-240 187.04 280.48L701.92 592 832 747.52V800z\" fill=\"currentColor\"/><path d=\"M672 448a96 96 0 1 0-96-96 96 96 0 0 0 96 96zm0-128a32 32 0 1 1-32 32 32 32 0 0 1 32-32z\" fill=\"currentColor\"/></svg>";
const WEBSCAN_OVERLAY_BUTTON_SIZE = 40;
const WEBSCAN_OVERLAY_BUTTON_EDGE = 8;
const WEBSCAN_OVERLAY_BUTTON_GAP = 10;
function getDefaultWebScanOverlayButtonStyle() {
return "width: " + WEBSCAN_OVERLAY_BUTTON_SIZE + "px; height: " + WEBSCAN_OVERLAY_BUTTON_SIZE + "px; padding: 0; border: 0; border-radius: 50%; background: rgba(0, 0, 0, 0.55); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10000;";
}
const currentScriptSrc = typeof document !== "undefined" const currentScriptSrc = typeof document !== "undefined"
&& document.currentScript && document.currentScript
&& document.currentScript.src; && document.currentScript.src;
@@ -204,6 +212,20 @@ function shouldMirrorWebVideoVertical() {
return getConfig("webScanVideoMirrorVertical") === true; return getConfig("webScanVideoMirrorVertical") === true;
} }
function getDefaultWebScanCanvasSizePx() {
if (typeof window === "undefined") {
return { width: 300, height: 300 };
}
if (isMobile()) {
const side = Math.min(window.innerWidth || 300, window.innerHeight || 300);
return {
width: Math.max(1, Math.round(side)),
height: Math.max(1, Math.round(side))
};
}
return { width: 300, height: 300 };
}
function getCanvasDisplaySize(canvasEl, fallbackWidth, fallbackHeight) { function getCanvasDisplaySize(canvasEl, fallbackWidth, fallbackHeight) {
const rect = canvasEl.getBoundingClientRect(); const rect = canvasEl.getBoundingClientRect();
const width = rect.width || parseFloat(canvasEl.style.width) || fallbackWidth; const width = rect.width || parseFloat(canvasEl.style.width) || fallbackWidth;
@@ -216,8 +238,15 @@ function getCanvasDisplaySize(canvasEl, fallbackWidth, fallbackHeight) {
function getWebScanCloseButtonPositionStyle(canvasEl) { function getWebScanCloseButtonPositionStyle(canvasEl) {
const rect = canvasEl.getBoundingClientRect(); const rect = canvasEl.getBoundingClientRect();
const top = Math.max(0, Math.round(rect.top + 6)); const top = Math.max(0, Math.round(rect.top + WEBSCAN_OVERLAY_BUTTON_EDGE));
const left = Math.max(0, Math.round(rect.right - 34)); const left = Math.max(0, Math.round(rect.right - (WEBSCAN_OVERLAY_BUTTON_SIZE + WEBSCAN_OVERLAY_BUTTON_EDGE)));
return "position: fixed; top: " + top + "px; left: " + left + "px;";
}
function getWebScanPickImageButtonPositionStyle(canvasEl) {
const rect = canvasEl.getBoundingClientRect();
const top = Math.max(0, Math.round(rect.top + WEBSCAN_OVERLAY_BUTTON_EDGE + WEBSCAN_OVERLAY_BUTTON_SIZE + WEBSCAN_OVERLAY_BUTTON_GAP));
const left = Math.max(0, Math.round(rect.right - (WEBSCAN_OVERLAY_BUTTON_SIZE + WEBSCAN_OVERLAY_BUTTON_EDGE)));
return "position: fixed; top: " + top + "px; left: " + left + "px;"; return "position: fixed; top: " + top + "px; left: " + left + "px;";
} }
@@ -226,11 +255,21 @@ function updateWebScanCloseButtonStyle(closeButtonEl, canvasEl) {
return; return;
} }
const customStyle = getConfig("webScanCloseButtonStyle") || ""; const customStyle = getConfig("webScanCloseButtonStyle") || "";
closeButtonEl.style.cssText = DEFAULT_WEBSCAN_CLOSE_BUTTON_STYLE closeButtonEl.style.cssText = getDefaultWebScanOverlayButtonStyle()
+ getWebScanCloseButtonPositionStyle(canvasEl) + getWebScanCloseButtonPositionStyle(canvasEl)
+ customStyle; + customStyle;
} }
function updateWebScanPickImageButtonStyle(pickButtonEl, canvasEl) {
if (!pickButtonEl) {
return;
}
const customStyle = getConfig("webScanPickImageButtonStyle") || "";
pickButtonEl.style.cssText = getDefaultWebScanOverlayButtonStyle()
+ getWebScanPickImageButtonPositionStyle(canvasEl)
+ customStyle;
}
function getCoverDrawOptions(sourceWidth, sourceHeight, targetWidth, targetHeight) { function getCoverDrawOptions(sourceWidth, sourceHeight, targetWidth, targetHeight) {
const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight); const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);
const width = sourceWidth * scale; const width = sourceWidth * scale;
@@ -438,7 +477,11 @@ export function startScanForWeb(onResult) {
"display: none", false); "display: none", false);
let canvasEnabled = getConfig("webScanCanvasEnabled") !== false; let canvasEnabled = getConfig("webScanCanvasEnabled") !== false;
let canvasDisplay = ""; let canvasDisplay = "";
let canvasBaseStyle = canvasStyle || (!!canvasClass ? "" : "position: fixed; width: 300px; height: 300px; top: 0; left: 0; z-index: 9999;"); let useDefaultCanvasLayout = !canvasStyle && !canvasClass;
let defaultCanvasPx = useDefaultCanvasLayout ? getDefaultWebScanCanvasSizePx() : { width: 300, height: 300 };
let canvasFallbackWidth = defaultCanvasPx.width;
let canvasFallbackHeight = defaultCanvasPx.height;
let canvasBaseStyle = canvasStyle || (!!canvasClass ? "" : "position: fixed; width: " + defaultCanvasPx.width + "px; height: " + defaultCanvasPx.height + "px; top: 0; left: 0; z-index: 9999;");
let canvasEl = createEl("canvas", let canvasEl = createEl("canvas",
"__webscan_canvas__", "__webscan_canvas__",
canvasBaseStyle + " display: none;", true); canvasBaseStyle + " display: none;", true);
@@ -446,9 +489,10 @@ export function startScanForWeb(onResult) {
canvasEl.style.cssText = canvasBaseStyle; canvasEl.style.cssText = canvasBaseStyle;
canvasEl.className = canvasClass || ""; canvasEl.className = canvasClass || "";
canvasDisplay = canvasEl.style.display; canvasDisplay = canvasEl.style.display;
let canvasDisplaySize = getCanvasDisplaySize(canvasEl, 300, 240); let canvasDisplaySize = getCanvasDisplaySize(canvasEl, canvasFallbackWidth, canvasFallbackHeight);
canvasEl.style.display = "none"; canvasEl.style.display = "none";
let closeButtonEl = null; let closeButtonEl = null;
let pickImageButtonEl = null;
if (canvasEnabled) { if (canvasEnabled) {
closeButtonEl = createEl("button", WEBSCAN_CLOSE_BUTTON_ID, "display: none;", true); closeButtonEl = createEl("button", WEBSCAN_CLOSE_BUTTON_ID, "display: none;", true);
closeButtonEl.type = "button"; closeButtonEl.type = "button";
@@ -463,6 +507,14 @@ export function startScanForWeb(onResult) {
} }
}; };
closeButtonEl.uuid = scanWeb.uuid; closeButtonEl.uuid = scanWeb.uuid;
if (isSupportImageScan()) {
pickImageButtonEl = createEl("button", WEBSCAN_PICK_IMAGE_BUTTON_ID, "display: none;", true);
pickImageButtonEl.type = "button";
pickImageButtonEl.className = getConfig("webScanPickImageButtonClass") || "";
pickImageButtonEl.innerHTML = WEBSCAN_PICK_IMAGE_ICON_SVG;
pickImageButtonEl.setAttribute("aria-label", "pick image");
pickImageButtonEl.uuid = scanWeb.uuid;
}
} }
let context = canvasEl.getContext("2d"); let context = canvasEl.getContext("2d");
videoEl.width = 300; videoEl.width = 300;
@@ -516,7 +568,40 @@ export function startScanForWeb(onResult) {
if (closeButtonEl) { if (closeButtonEl) {
closeButtonEl.style.display = "none"; closeButtonEl.style.display = "none";
} }
if (pickImageButtonEl) {
pickImageButtonEl.style.display = "none";
}
}; };
if (pickImageButtonEl) {
pickImageButtonEl.onclick = event => {
event.preventDefault && event.preventDefault();
event.stopPropagation && event.stopPropagation();
if (scanWeb.uuid !== currentUuid || closed) {
return;
}
chooseImageFile().then(file => {
if (!file || scanWeb.uuid !== currentUuid || closed) {
return;
}
detectImageFile(detector, file).then(code => {
if (!code || !code.rawValue || scanWeb.uuid !== currentUuid || closed) {
return;
}
if (!onResult || !onResult(code.rawValue)) {
return;
}
playScanBeep();
scanWeb.uuid = null;
scanWeb.finish = true;
close();
resolve({
result: code.rawValue
});
}).catch(() => {
});
});
};
}
let tick = () => { let tick = () => {
try { try {
if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) { if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) {
@@ -536,8 +621,13 @@ export function startScanForWeb(onResult) {
if (canvasEnabled && !displayed) { if (canvasEnabled && !displayed) {
displayed = true; displayed = true;
canvasEl.style.display = canvasDisplay || ""; canvasEl.style.display = canvasDisplay || "";
canvasDisplaySize = getCanvasDisplaySize(canvasEl, canvasFallbackWidth, canvasFallbackHeight);
updateWebScanCloseButtonStyle(closeButtonEl, canvasEl); updateWebScanCloseButtonStyle(closeButtonEl, canvasEl);
closeButtonEl && (closeButtonEl.style.display = "flex"); closeButtonEl && (closeButtonEl.style.display = "flex");
if (pickImageButtonEl) {
updateWebScanPickImageButtonStyle(pickImageButtonEl, canvasEl);
pickImageButtonEl.style.display = "flex";
}
} }
detecting = true; detecting = true;
detector.detect(videoEl).then(barcodes => { detector.detect(videoEl).then(barcodes => {
@@ -591,5 +681,6 @@ export function startScanForWeb(onResult) {
removeEl("__webscan_video__", currentUuid); removeEl("__webscan_video__", currentUuid);
removeEl("__webscan_canvas__", currentUuid); removeEl("__webscan_canvas__", currentUuid);
removeEl(WEBSCAN_CLOSE_BUTTON_ID, currentUuid); removeEl(WEBSCAN_CLOSE_BUTTON_ID, currentUuid);
removeEl(WEBSCAN_PICK_IMAGE_BUTTON_ID, currentUuid);
}) })
} }

10
types/index.d.ts vendored
View File

@@ -54,7 +54,7 @@ interface ScanConfigOptions {
*/ */
webScanCanvasEnabled?: boolean, webScanCanvasEnabled?: boolean,
/** /**
* 网页扫码canvas样式默认position: fixed; width: 300px; height: 300px; top: 0; left: 0; z-index: 9999; * 网页扫码canvas样式默认PC 为左上角固定 300×300移动端为 min(视口宽, 视口高) 的正方形,固定在左上角;z-index: 9999
*/ */
webScanCanvasStyle?: string, webScanCanvasStyle?: string,
/** /**
@@ -69,6 +69,14 @@ interface ScanConfigOptions {
* 网页扫码关闭按钮的 class * 网页扫码关闭按钮的 class
*/ */
webScanCloseButtonClass?: string, webScanCloseButtonClass?: string,
/**
* Canvas 开启且支持图片识别时关闭按钮下方的「选图」按钮样式fixed 定位基准由 SDK 计算,可与关闭按钮一致覆盖)
*/
webScanPickImageButtonStyle?: string,
/**
* 网页扫码选图按钮的 class
*/
webScanPickImageButtonClass?: string,
/** /**
* 网页扫码类型,默认支持二维码和条码 * 网页扫码类型,默认支持二维码和条码
*/ */