chore: prepare release; reintroduce front camera CSS mirror, stabilize QR Scanner

This commit is contained in:
2026-02-27 19:02:49 +00:00
parent 613604f3c4
commit 5b1a50f148
2 changed files with 114 additions and 1 deletions

View File

@@ -9,12 +9,60 @@ const scannedCodes = ref([])
const hasMultipleCameras = ref(false) const hasMultipleCameras = ref(false)
const isFullscreen = ref(false) const isFullscreen = ref(false)
const videoAspect = ref(1) const videoAspect = ref(1)
const isFront = computed(() => facingMode.value === 'user')
const wrapperRef = ref(null)
let frontMirrorObserver = null
const bgCanvas = ref(null)
let bgRafId = null
const updateVideoAspect = () => { const updateVideoAspect = () => {
const videoEl = document.querySelector('.camera-wrapper video') const videoEl = document.querySelector('.camera-wrapper video')
if (videoEl && videoEl.videoWidth && videoEl.videoHeight) { if (videoEl && videoEl.videoWidth && videoEl.videoHeight) {
videoAspect.value = videoEl.videoWidth / videoEl.videoHeight videoAspect.value = videoEl.videoWidth / videoEl.videoHeight
} }
} }
const startBackgroundLoop = () => {
const draw = () => {
const videoEl = document.querySelector('.camera-wrapper video')
const canvas = bgCanvas.value
if (!videoEl || !canvas) {
bgRafId = requestAnimationFrame(draw)
return
}
const vw = videoEl.videoWidth || 0
const vh = videoEl.videoHeight || 0
if (!vw || !vh) {
bgRafId = requestAnimationFrame(draw)
return
}
const cw = Math.floor(window.innerWidth)
const ch = Math.floor(window.innerHeight * 0.5)
if (canvas.width !== cw || canvas.height !== ch) {
canvas.width = cw
canvas.height = ch
}
const ctx = canvas.getContext('2d')
if (ctx) {
// cover horizontally: scale by width, crop top/bottom
const scale = cw / vw
const srcH = ch / scale
const sx = 0
const sy = Math.max(0, (vh - srcH) / 2)
ctx.clearRect(0, 0, cw, ch)
ctx.drawImage(videoEl, sx, sy, vw, srcH, 0, 0, cw, ch)
}
bgRafId = requestAnimationFrame(draw)
}
if (bgRafId) cancelAnimationFrame(bgRafId)
bgRafId = requestAnimationFrame(draw)
}
// front mirror canvas removed to restore stable behavior
const stopBackgroundLoop = () => {
if (bgRafId) {
cancelAnimationFrame(bgRafId)
bgRafId = null
}
}
const desktopFullscreenStyle = computed(() => { const desktopFullscreenStyle = computed(() => {
if (!isFullscreen.value) return {} if (!isFullscreen.value) return {}
const isDesktop = window.innerWidth >= 768 const isDesktop = window.innerWidth >= 768
@@ -95,8 +143,26 @@ const onDetect = (detectedCodes) => {
const onCameraOn = async (capabilities) => { const onCameraOn = async (capabilities) => {
// Camera is ready // Camera is ready
setTimeout(updateVideoAspect, 100) setTimeout(updateVideoAspect, 100)
setTimeout(startBackgroundLoop, 150)
// Flip is handled via global CSS; no JS flips needed
} }
const ensureFrontMirror = () => {
// No-op: mirror is applied via CSS selectors
}
const startFrontMirrorObserver = () => {
// No-op: mirror is applied via CSS selectors
}
const stopFrontMirrorObserver = () => {
// No-op
}
watch(isFront, () => {
// CSS-based; nothing to do
})
const paintDetections = (detectedCodes, ctx) => { const paintDetections = (detectedCodes, ctx) => {
try { try {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
@@ -171,10 +237,20 @@ onMounted(() => {
checkCameras() checkCameras()
loadHistory() loadHistory()
window.addEventListener('resize', updateVideoAspect) window.addEventListener('resize', updateVideoAspect)
window.addEventListener('resize', startBackgroundLoop)
watch(isFullscreen, (fs) => {
if (fs) {
startBackgroundLoop()
} else {
stopBackgroundLoop()
}
}, { immediate: true })
}) })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', updateVideoAspect) window.removeEventListener('resize', updateVideoAspect)
window.removeEventListener('resize', startBackgroundLoop)
stopBackgroundLoop()
}) })
const switchCamera = (event) => { const switchCamera = (event) => {
@@ -244,11 +320,22 @@ const isUrl = (string) => {
<Teleport to="body" :disabled="!isFullscreen"> <Teleport to="body" :disabled="!isFullscreen">
<div class="scanner-content" :class="{ 'is-fullscreen': isFullscreen }"> <div class="scanner-content" :class="{ 'is-fullscreen': isFullscreen }">
<canvas
v-if="isFullscreen"
ref="bgCanvas"
class="camera-bg"
></canvas>
<button v-if="isFullscreen" class="close-fullscreen-btn" @click="toggleFullscreen"> <button v-if="isFullscreen" class="close-fullscreen-btn" @click="toggleFullscreen">
<X size="24" /> <X size="24" />
</button> </button>
<div class="camera-wrapper" :class="{ 'clickable': !isFullscreen }" :style="desktopFullscreenStyle" @click="!isFullscreen && toggleFullscreen()"> <div
class="camera-wrapper"
:class="{ 'clickable': !isFullscreen, 'is-front': isFront }"
:style="desktopFullscreenStyle"
ref="wrapperRef"
@click="!isFullscreen && toggleFullscreen()"
>
<QrcodeStream <QrcodeStream
:constraints="{ facingMode }" :constraints="{ facingMode }"
@detect="onDetect" @detect="onDetect"
@@ -384,9 +471,22 @@ const isUrl = (string) => {
border-radius: 0; border-radius: 0;
border: none; border: none;
margin: 0; margin: 0;
z-index: 1;
}
.camera-bg {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 50vh;
filter: blur(16px) saturate(110%);
opacity: 0.9;
z-index: 0; z-index: 0;
} }
/* front mirror canvas removed */
.error-overlay { .error-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -614,6 +714,17 @@ const isUrl = (string) => {
inset: 0 !important; inset: 0 !important;
} }
/* Front camera mirror (CSS-only) */
.camera-wrapper.is-front :deep(video) {
transform: scaleX(-1);
transform-origin: center;
}
.camera-wrapper.is-front :deep(#qrcode-stream-pause-frame),
.camera-wrapper.is-front :deep(#qrcode-stream-overlay) {
transform: scaleX(-1);
transform-origin: center;
}
@media (min-width: 768px) { @media (min-width: 768px) {
:deep(.scanner-content.is-fullscreen .camera-wrapper video) { :deep(.scanner-content.is-fullscreen .camera-wrapper video) {
object-fit: contain !important; object-fit: contain !important;

View File

@@ -125,6 +125,8 @@ body {
} }
} }
/* Removed global front camera mirror to restore stability */
/* --- Shared styles for all tools (moved from tools.css) --- */ /* --- Shared styles for all tools (moved from tools.css) --- */
.tool-container { .tool-container {