Compare commits
2 Commits
10c9e576e3
...
4602ec9e74
| Author | SHA1 | Date | |
|---|---|---|---|
| 4602ec9e74 | |||
| 520811e883 |
22
src/App.vue
22
src/App.vue
@ -18,6 +18,7 @@ const autoFitOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let viewportWidth = document.documentElement.clientWidth
|
let viewportWidth = document.documentElement.clientWidth
|
||||||
|
let autoFitResetTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
const initAutoFit = () => {
|
const initAutoFit = () => {
|
||||||
autofit.init(autoFitOptions, false)
|
autofit.init(autoFitOptions, false)
|
||||||
@ -29,29 +30,36 @@ const resetAutoFit = () => {
|
|||||||
viewportWidth = document.documentElement.clientWidth
|
viewportWidth = document.documentElement.clientWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scheduleAutoFitReset = () => {
|
||||||
|
clearTimeout(autoFitResetTimer)
|
||||||
|
// 等待旋转动画和视口尺寸稳定,避免 resize 期间反复重建 autofit。
|
||||||
|
autoFitResetTimer = setTimeout(resetAutoFit, 50)
|
||||||
|
}
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
const nextWidth = document.documentElement.clientWidth
|
const nextWidth = document.documentElement.clientWidth
|
||||||
|
|
||||||
// 软键盘通常只改变可视高度;宽度不变时保持原缩放比例。
|
// 软键盘通常只改变可视高度;宽度不变时保持原缩放比例。
|
||||||
if (Math.abs(nextWidth - viewportWidth) <= 2) return
|
if (Math.abs(nextWidth - viewportWidth) <= 2) return
|
||||||
|
|
||||||
resetAutoFit()
|
scheduleAutoFitReset()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initAutoFit()
|
initAutoFit()
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
window.addEventListener('orientationchange', resetAutoFit)
|
window.addEventListener('orientationchange', scheduleAutoFitReset)
|
||||||
document.addEventListener('fullscreenchange', resetAutoFit)
|
document.addEventListener('fullscreenchange', scheduleAutoFitReset)
|
||||||
document.addEventListener('webkitfullscreenchange', resetAutoFit)
|
document.addEventListener('webkitfullscreenchange', scheduleAutoFitReset)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
autofit.off('#app')
|
autofit.off('#app')
|
||||||
|
clearTimeout(autoFitResetTimer)
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
window.removeEventListener('orientationchange', resetAutoFit)
|
window.removeEventListener('orientationchange', scheduleAutoFitReset)
|
||||||
document.removeEventListener('fullscreenchange', resetAutoFit)
|
document.removeEventListener('fullscreenchange', scheduleAutoFitReset)
|
||||||
document.removeEventListener('webkitfullscreenchange', resetAutoFit)
|
document.removeEventListener('webkitfullscreenchange', scheduleAutoFitReset)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'
|
|||||||
const isPortraitTablet = ref(false)
|
const isPortraitTablet = ref(false)
|
||||||
|
|
||||||
let portraitMediaQuery
|
let portraitMediaQuery
|
||||||
|
let orientationFrame
|
||||||
|
const orientationTimers = new Set()
|
||||||
|
|
||||||
const isTablet = () => {
|
const isTablet = () => {
|
||||||
const userAgent = navigator.userAgent
|
const userAgent = navigator.userAgent
|
||||||
@ -40,11 +42,32 @@ const isTablet = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateOrientation = () => {
|
const updateOrientation = () => {
|
||||||
const isPortrait = portraitMediaQuery?.matches ?? window.innerHeight > window.innerWidth
|
const viewport = window.visualViewport
|
||||||
|
const viewportWidth = viewport?.width || window.innerWidth
|
||||||
|
const viewportHeight = viewport?.height || window.innerHeight
|
||||||
|
const isPortrait = viewportHeight > viewportWidth
|
||||||
isPortraitTablet.value = isTablet() && isPortrait
|
isPortraitTablet.value = isTablet() && isPortrait
|
||||||
document.documentElement.classList.toggle('is-portrait-tablet', isPortraitTablet.value)
|
document.documentElement.classList.toggle('is-portrait-tablet', isPortraitTablet.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scheduleOrientationUpdate = () => {
|
||||||
|
updateOrientation()
|
||||||
|
|
||||||
|
cancelAnimationFrame(orientationFrame)
|
||||||
|
orientationFrame = requestAnimationFrame(updateOrientation)
|
||||||
|
|
||||||
|
orientationTimers.forEach(clearTimeout)
|
||||||
|
orientationTimers.clear()
|
||||||
|
// iPad 旋转动画期间视口尺寸会分阶段更新,短时间复检避免停留在旧方向。
|
||||||
|
for (const delay of [80, 180, 350]) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
orientationTimers.delete(timer)
|
||||||
|
updateOrientation()
|
||||||
|
}, delay)
|
||||||
|
orientationTimers.add(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tryLockLandscape = async () => {
|
const tryLockLandscape = async () => {
|
||||||
if (!isTablet() || !screen.orientation?.lock) return
|
if (!isTablet() || !screen.orientation?.lock) return
|
||||||
|
|
||||||
@ -60,17 +83,22 @@ onMounted(() => {
|
|||||||
updateOrientation()
|
updateOrientation()
|
||||||
tryLockLandscape()
|
tryLockLandscape()
|
||||||
|
|
||||||
portraitMediaQuery.addEventListener?.('change', updateOrientation)
|
portraitMediaQuery.addEventListener?.('change', scheduleOrientationUpdate)
|
||||||
window.addEventListener('resize', updateOrientation)
|
window.addEventListener('resize', scheduleOrientationUpdate)
|
||||||
window.addEventListener('orientationchange', updateOrientation)
|
window.addEventListener('orientationchange', scheduleOrientationUpdate)
|
||||||
|
window.visualViewport?.addEventListener('resize', scheduleOrientationUpdate)
|
||||||
document.addEventListener('fullscreenchange', tryLockLandscape)
|
document.addEventListener('fullscreenchange', tryLockLandscape)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
document.documentElement.classList.remove('is-portrait-tablet')
|
document.documentElement.classList.remove('is-portrait-tablet')
|
||||||
portraitMediaQuery?.removeEventListener?.('change', updateOrientation)
|
cancelAnimationFrame(orientationFrame)
|
||||||
window.removeEventListener('resize', updateOrientation)
|
orientationTimers.forEach(clearTimeout)
|
||||||
window.removeEventListener('orientationchange', updateOrientation)
|
orientationTimers.clear()
|
||||||
|
portraitMediaQuery?.removeEventListener?.('change', scheduleOrientationUpdate)
|
||||||
|
window.removeEventListener('resize', scheduleOrientationUpdate)
|
||||||
|
window.removeEventListener('orientationchange', scheduleOrientationUpdate)
|
||||||
|
window.visualViewport?.removeEventListener('resize', scheduleOrientationUpdate)
|
||||||
document.removeEventListener('fullscreenchange', tryLockLandscape)
|
document.removeEventListener('fullscreenchange', tryLockLandscape)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -94,6 +94,9 @@ const containerRef = ref(null)
|
|||||||
const mapCanvasRef = ref(null)
|
const mapCanvasRef = ref(null)
|
||||||
let resizeObserver
|
let resizeObserver
|
||||||
let eventsBound = false
|
let eventsBound = false
|
||||||
|
let orientationObserver
|
||||||
|
let animationFrameId = null
|
||||||
|
let isRendering = false
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
mapCanvas = mapCanvasRef.value;
|
mapCanvas = mapCanvasRef.value;
|
||||||
@ -106,11 +109,14 @@ const init = () => {
|
|||||||
*/
|
*/
|
||||||
function resizeCanvasToContainer() {
|
function resizeCanvasToContainer() {
|
||||||
if (!mapCanvas || !containerRef.value) return;
|
if (!mapCanvas || !containerRef.value) return;
|
||||||
const containerRect = containerRef.value.getBoundingClientRect(); // 获取容器实际尺寸
|
// clientWidth/clientHeight 是布局坐标,不受 autofit.js 的 CSS transform 影响。
|
||||||
if (!containerRect.width || !containerRect.height) return;
|
// getBoundingClientRect() 返回的是缩放后的屏幕尺寸,不能用于 Canvas 逻辑尺寸。
|
||||||
|
const containerWidth = containerRef.value.clientWidth;
|
||||||
|
const containerHeight = containerRef.value.clientHeight;
|
||||||
|
if (!containerWidth || !containerHeight) return;
|
||||||
applicationState.devicePixelRatio = window.devicePixelRatio || 1; // 获取设备像素比
|
applicationState.devicePixelRatio = window.devicePixelRatio || 1; // 获取设备像素比
|
||||||
applicationState.canvasWidthPx = containerRect.width; // 更新逻辑宽度
|
applicationState.canvasWidthPx = containerWidth; // 更新逻辑宽度
|
||||||
applicationState.canvasHeightPx = containerRect.height; // 逻辑高度 = 容器高度 - 状态栏高度
|
applicationState.canvasHeightPx = containerHeight; // 逻辑高度 = 容器高度 - 状态栏高度
|
||||||
mapCanvas.width = applicationState.canvasWidthPx * applicationState.devicePixelRatio; // Canvas 物理像素宽
|
mapCanvas.width = applicationState.canvasWidthPx * applicationState.devicePixelRatio; // Canvas 物理像素宽
|
||||||
mapCanvas.height = applicationState.canvasHeightPx * applicationState.devicePixelRatio; // Canvas 物理像素高
|
mapCanvas.height = applicationState.canvasHeightPx * applicationState.devicePixelRatio; // Canvas 物理像素高
|
||||||
mapCanvas.style.width = applicationState.canvasWidthPx + 'px'; // CSS 显示宽度
|
mapCanvas.style.width = applicationState.canvasWidthPx + 'px'; // CSS 显示宽度
|
||||||
@ -211,6 +217,8 @@ async function loadMapIntoApplication(parsedMap) {
|
|||||||
* @param {number} timestamp - requestAnimationFrame 提供的时间戳(毫秒)
|
* @param {number} timestamp - requestAnimationFrame 提供的时间戳(毫秒)
|
||||||
*/
|
*/
|
||||||
function renderFrame(timestamp) {
|
function renderFrame(timestamp) {
|
||||||
|
if (!isRendering || !canvasCtx) return;
|
||||||
|
|
||||||
// 更新动画时间
|
// 更新动画时间
|
||||||
applicationState.animationTimeSeconds = (timestamp || 0) / 1000;
|
applicationState.animationTimeSeconds = (timestamp || 0) / 1000;
|
||||||
|
|
||||||
@ -220,7 +228,7 @@ function renderFrame(timestamp) {
|
|||||||
|
|
||||||
// 如果地图未加载,跳过绘制
|
// 如果地图未加载,跳过绘制
|
||||||
if (!applicationState.isMapLoaded) {
|
if (!applicationState.isMapLoaded) {
|
||||||
requestAnimationFrame(renderFrame); // 继续请求下一帧
|
animationFrameId = requestAnimationFrame(renderFrame); // 继续请求下一帧
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +278,36 @@ function renderFrame(timestamp) {
|
|||||||
drawMapBoundary(canvasCtx, map, applicationState);
|
drawMapBoundary(canvasCtx, map, applicationState);
|
||||||
|
|
||||||
// 请求下一帧渲染
|
// 请求下一帧渲染
|
||||||
requestAnimationFrame(renderFrame);
|
animationFrameId = requestAnimationFrame(renderFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopRendering = () => {
|
||||||
|
isRendering = false
|
||||||
|
if (animationFrameId !== null) {
|
||||||
|
cancelAnimationFrame(animationFrameId)
|
||||||
|
animationFrameId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startRendering = () => {
|
||||||
|
if (isRendering || !applicationState.isMapLoaded || document.documentElement.classList.contains('is-portrait-tablet')) return
|
||||||
|
isRendering = true
|
||||||
|
animationFrameId = requestAnimationFrame(renderFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOrientationVisibility = () => {
|
||||||
|
if (document.documentElement.classList.contains('is-portrait-tablet')) {
|
||||||
|
stopRendering()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// v-show 恢复后等待 Vue 和浏览器完成布局,再重设画布并恢复渲染。
|
||||||
|
nextTick(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
resizeCanvasToContainer()
|
||||||
|
startRendering()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const drawAllRobots = (ctx) => {
|
const drawAllRobots = (ctx) => {
|
||||||
@ -292,12 +329,25 @@ const bindEvent = () => {
|
|||||||
let activePointerId = null
|
let activePointerId = null
|
||||||
let lastPointerX = 0
|
let lastPointerX = 0
|
||||||
let lastPointerY = 0
|
let lastPointerY = 0
|
||||||
|
let pointerStartX = 0
|
||||||
|
let pointerStartY = 0
|
||||||
|
let hasDragged = false
|
||||||
|
|
||||||
|
const getCanvasPointerPosition = (pointerEvent) => {
|
||||||
|
const canvasRect = mapCanvas.getBoundingClientRect()
|
||||||
|
const scaleX = applicationState.canvasWidthPx / canvasRect.width
|
||||||
|
const scaleY = applicationState.canvasHeightPx / canvasRect.height
|
||||||
|
return {
|
||||||
|
x: (pointerEvent.clientX - canvasRect.left) * scaleX,
|
||||||
|
y: (pointerEvent.clientY - canvasRect.top) * scaleY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pointer Events 同时支持鼠标、触控笔和 Pad 触摸。
|
// Pointer Events 同时支持鼠标、触控笔和 Pad 触摸。
|
||||||
mapCanvas.addEventListener('pointermove', (pointerEvent) => {
|
mapCanvas.addEventListener('pointermove', (pointerEvent) => {
|
||||||
const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置
|
const pointerPosition = getCanvasPointerPosition(pointerEvent)
|
||||||
applicationState.mouseState.screenX = pointerEvent.clientX - canvasRect.left;
|
applicationState.mouseState.screenX = pointerPosition.x;
|
||||||
applicationState.mouseState.screenY = pointerEvent.clientY - canvasRect.top;
|
applicationState.mouseState.screenY = pointerPosition.y;
|
||||||
// 反算世界坐标
|
// 反算世界坐标
|
||||||
const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState);
|
const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState);
|
||||||
applicationState.mouseState.worldX = worldPos.x;
|
applicationState.mouseState.worldX = worldPos.x;
|
||||||
@ -305,12 +355,15 @@ const bindEvent = () => {
|
|||||||
|
|
||||||
if (applicationState.mouseState.isDragging && pointerEvent.pointerId === activePointerId) {
|
if (applicationState.mouseState.isDragging && pointerEvent.pointerId === activePointerId) {
|
||||||
pointerEvent.preventDefault()
|
pointerEvent.preventDefault()
|
||||||
const movementX = pointerEvent.clientX - lastPointerX
|
const movementX = pointerPosition.x - lastPointerX
|
||||||
const movementY = pointerEvent.clientY - lastPointerY
|
const movementY = pointerPosition.y - lastPointerY
|
||||||
|
if (Math.hypot(pointerPosition.x - pointerStartX, pointerPosition.y - pointerStartY) > 6) {
|
||||||
|
hasDragged = true
|
||||||
|
}
|
||||||
applicationState.camera.centerX -= movementX / applicationState.camera.pixelsPerMeter;
|
applicationState.camera.centerX -= movementX / applicationState.camera.pixelsPerMeter;
|
||||||
applicationState.camera.centerY += movementY / applicationState.camera.pixelsPerMeter;
|
applicationState.camera.centerY += movementY / applicationState.camera.pixelsPerMeter;
|
||||||
lastPointerX = pointerEvent.clientX
|
lastPointerX = pointerPosition.x
|
||||||
lastPointerY = pointerEvent.clientY
|
lastPointerY = pointerPosition.y
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointerEvent.pointerType === 'mouse') {
|
if (pointerEvent.pointerType === 'mouse') {
|
||||||
@ -319,9 +372,7 @@ const bindEvent = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mapCanvas.addEventListener('click', (mouseEvent) => {
|
mapCanvas.addEventListener('click', (mouseEvent) => {
|
||||||
const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置
|
const { x: screenX, y: screenY } = getCanvasPointerPosition(mouseEvent)
|
||||||
const screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X
|
|
||||||
const screenY = mouseEvent.clientY - canvasRect.top; // 鼠标在 Canvas 上的 Y
|
|
||||||
const worldPos = screenToWorld(screenX, screenY, applicationState);
|
const worldPos = screenToWorld(screenX, screenY, applicationState);
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -331,15 +382,33 @@ const bindEvent = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pointerEvent.preventDefault()
|
pointerEvent.preventDefault()
|
||||||
|
const pointerPosition = getCanvasPointerPosition(pointerEvent)
|
||||||
activePointerId = pointerEvent.pointerId
|
activePointerId = pointerEvent.pointerId
|
||||||
lastPointerX = pointerEvent.clientX
|
lastPointerX = pointerPosition.x
|
||||||
lastPointerY = pointerEvent.clientY
|
lastPointerY = pointerPosition.y
|
||||||
|
pointerStartX = pointerPosition.x
|
||||||
|
pointerStartY = pointerPosition.y
|
||||||
|
hasDragged = false
|
||||||
applicationState.mouseState.isDragging = true;
|
applicationState.mouseState.isDragging = true;
|
||||||
mapCanvas.setPointerCapture(pointerEvent.pointerId)
|
mapCanvas.setPointerCapture(pointerEvent.pointerId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const endPointerDrag = (pointerEvent) => {
|
const endPointerDrag = (pointerEvent, cancelled = false) => {
|
||||||
if (pointerEvent.pointerId !== activePointerId) return
|
if (pointerEvent.pointerId !== activePointerId) return
|
||||||
|
|
||||||
|
// Pad/触控笔轻点时执行命中检测;轻点空白区域会隐藏信息窗。
|
||||||
|
if (!cancelled && !hasDragged && pointerEvent.pointerType !== 'mouse') {
|
||||||
|
const pointerPosition = getCanvasPointerPosition(pointerEvent)
|
||||||
|
applicationState.mouseState.screenX = pointerPosition.x
|
||||||
|
applicationState.mouseState.screenY = pointerPosition.y
|
||||||
|
const worldPos = screenToWorld(
|
||||||
|
applicationState.mouseState.screenX,
|
||||||
|
applicationState.mouseState.screenY,
|
||||||
|
applicationState
|
||||||
|
)
|
||||||
|
updateTooltipDisplay(hitTestRobots(worldPos.x, worldPos.y, applicationState))
|
||||||
|
}
|
||||||
|
|
||||||
applicationState.mouseState.isDragging = false;
|
applicationState.mouseState.isDragging = false;
|
||||||
if (mapCanvas.hasPointerCapture(pointerEvent.pointerId)) {
|
if (mapCanvas.hasPointerCapture(pointerEvent.pointerId)) {
|
||||||
mapCanvas.releasePointerCapture(pointerEvent.pointerId)
|
mapCanvas.releasePointerCapture(pointerEvent.pointerId)
|
||||||
@ -347,8 +416,8 @@ const bindEvent = () => {
|
|||||||
activePointerId = null
|
activePointerId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
mapCanvas.addEventListener('pointerup', endPointerDrag);
|
mapCanvas.addEventListener('pointerup', (pointerEvent) => endPointerDrag(pointerEvent));
|
||||||
mapCanvas.addEventListener('pointercancel', endPointerDrag);
|
mapCanvas.addEventListener('pointercancel', (pointerEvent) => endPointerDrag(pointerEvent, true));
|
||||||
|
|
||||||
// ── 滚轮:缩放(向鼠标位置缩放)──
|
// ── 滚轮:缩放(向鼠标位置缩放)──
|
||||||
mapCanvas.addEventListener('wheel', (wheelEvent) => {
|
mapCanvas.addEventListener('wheel', (wheelEvent) => {
|
||||||
@ -460,6 +529,11 @@ onMounted(() => {
|
|||||||
resizeObserver = new ResizeObserver(resizeCanvasToContainer)
|
resizeObserver = new ResizeObserver(resizeCanvasToContainer)
|
||||||
resizeObserver.observe(containerRef.value)
|
resizeObserver.observe(containerRef.value)
|
||||||
window.addEventListener('resize', resizeCanvasToContainer)
|
window.addEventListener('resize', resizeCanvasToContainer)
|
||||||
|
orientationObserver = new MutationObserver(handleOrientationVisibility)
|
||||||
|
orientationObserver.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class']
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.mapName,
|
watch(() => props.mapName,
|
||||||
@ -467,7 +541,7 @@ watch(() => props.mapName,
|
|||||||
if (newVal) {
|
if (newVal) {
|
||||||
await loadSourceMap(newVal)
|
await loadSourceMap(newVal)
|
||||||
addRobot(robotStore.ip, robotStore.ip, robotStore.position.x, robotStore.position.y, robotStore.position.theta, getThemeColor('--color-canvas-robot'))
|
addRobot(robotStore.ip, robotStore.ip, robotStore.position.x, robotStore.position.y, robotStore.position.theta, getThemeColor('--color-canvas-robot'))
|
||||||
requestAnimationFrame(renderFrame);
|
startRendering()
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
immediate: true
|
immediate: true
|
||||||
@ -480,8 +554,10 @@ defineExpose({
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
stopRendering()
|
||||||
window.removeEventListener('resize', resizeCanvasToContainer)
|
window.removeEventListener('resize', resizeCanvasToContainer)
|
||||||
resizeObserver?.disconnect()
|
resizeObserver?.disconnect()
|
||||||
|
orientationObserver?.disconnect()
|
||||||
canvasCtx = null
|
canvasCtx = null
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user