I'm using the Vue 3 Composition api and Typescript to implement pinch-to-zoom using the HammerJS package.
I'm trying to follow CodePen's working example written in JavaScript: https://codepen.io/bakho/details/GBzvbB implemented in Vue.
However, I ran into some issues when trying to get it running in my Vue app and I'm not sure how to fix it.
The following error:
// Object is possibly 'null'. imageContainer.value.offsetWidth; // Object is possibly 'null'. imageContainer.value.appendChild(displayImage); // Object is possibly 'null'. imageContainer.value.addEventList ener...
This is the complete source code:
<template> <h1>Image Zoom</h1> <div class="imageContainer" ref="imageContainer"></div> </template> <script lang="ts"> import Hammer from "hammerjs"; import { defineComponent } from "vue"; import { ref } from 'vue'; export default defineComponent({ setup() { const imageUrl = "https://source.unsplash.com/random"; const imageContainer = ref(null) let minScale = 1; let maxScale = 4; let imageWidth : any; let imageHeight : any; let containerWidth : any; let containerHeight : any; let displayImageX = 0; let displayImageY = 0; let displayImageScale = 1; let displayDefaultWidth : any; let displayDefaultHeight let rangeX = 0; let rangeMaxX = 0; let rangeMinX = 0; let rangeY = 0; let rangeMaxY = 0; let rangeMinY = 0; // let displayImageRangeY = 0; let displayImageCurrentX = 0; let displayImageCurrentY = 0; let displayImageCurrentScale = 1; function resizeContainer() { containerWidth = imageContainer.value.offsetWidth; containerHeight = imageContainer.value.offsetHeight; if (displayDefaultWidth !== undefined && displayDefaultHeight !== undefined) { displayDefaultWidth = displayImage.offsetWidth; displayDefaultHeight = displayImage.offsetHeight; updateRange(); displayImageCurrentX = clamp(displayImageX, rangeMinX, rangeMaxX); displayImageCurrentY = clamp(displayImageY, rangeMinY, rangeMaxY); updateDisplayImage( displayImageCurrentX, displayImageCurrentY, displayImageCurrentScale ); } } resizeContainer(); function clamp(value, min, max) { return Math.min(Math.max(min, value), max); } function clampScale(newScale) { return clamp(newScale, minScale, maxScale); } const displayImage = new Image(); displayImage.src = imageUrl; displayImage.onload = function(){ imageWidth = displayImage.width; imageHeight = displayImage.height; imageContainer.value.appendChild(displayImage); displayImage.addEventListe ner('mousedown', e => e.preventDefault(), false); displayDefaultWidth = displayImage.offsetWidth; displayDefaultHeight = displayImage.offsetHeight; rangeX = Math.max(0, displayDefaultWidth - containerWidth); rangeY = Math.max(0, displayDefaultHeight - containerHeight); } imageContainer.value.addEventLis tener('wheel', e => { displayImageScale = displayImageCurrentScale = clampScale(displayImageScale + (e.wheelDelta / 800)); updateRange(); displayImageCurrentX = clamp(displayImageCurrentX, rangeMinX, rangeMaxX) displayImageCurrentY = clamp(displayImageCurrentY, rangeMinY, rangeMaxY) updateDisplayImage(displayImageCurrentX, displayImageCurrentY, displayImageScale); }, false); function updateDisplayImage(x, y, scale) { const transform = 'translateX(' + x + 'px) translateY(' + y + 'px) translateZ(0px) scale(' + scale + ',' + scale + ')'; displayImage.style.transform = transform; displayImage.style.webkitTransform = transform; displayImage.style.transform = transform; } function updateRange() { rangeX = Math.max(0, Math.round(displayDefaultWidth * displayImageCurrentScale) - containerWidth); rangeY = Math.max(0, Math.round(displayDefaultHeight * displayImageCurrentScale) - containerHeight); rangeMaxX = Math.round(rangeX / 2); rangeMinX = 0 - rangeMaxX; rangeMaxY = Math.round(rangeY / 2); rangeMinY = 0 - rangeMaxY; } const hammertime = new Hammer(imageContainer); hammertime.get('pinch').set({ enable: true }); hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); hammertime.on('pan', ev => { displayImageCurrentX = clamp(displayImageX + ev.deltaX, rangeMinX, rangeMaxX); displayImageCurrentY = clamp(displayImageY + ev.deltaY, rangeMinY, rangeMaxY); updateDisplayImage(displayImageCurrentX, displayImageCurrentY, displayImageScale); }); hammertime.on('pinch pinchmove', ev => { displayImageCurrentScale = clampScale(ev.scale * displayImageScale); updateRange(); displayImageCurrentX = clamp(displayImageX + ev.deltaX, rangeMinX, rangeMaxX); displayImageCurrentY = clamp(displayImageY + ev.deltaY, rangeMinY, rangeMaxY); updateDisplayImage(displayImageCurrentX, displayImageCurrentY, displayImageCurrentScale); }); hammertime.on('panend pancancel pinchend pinchcancel', () => { displayImageScale = displayImageCurrentScale; displayImageX = displayImageCurrentX; displayImageY = displayImageCurrentY; }); return {}; }, }); </script> <style> .imageContainer { width: 96%; height: 96%; max-width: 800px; max-height: 600px; position: absolute; overflow: hidden; top: 0; right: 0; bottom: 0; left: 0; margin: auto; background: #2b2b2c; display: flex; flex-direction: column; align-items: center; justify-content: center; } .imageContainer > img { display: block; max-width: 100%; max-height: 100%; cursor: move; touch-action: none; } </style>
Can anyone tell me what is going wrong and why it is causing this Object may be "null"
P粉7528260082023-11-09 00:11:04
This is the reason for this error:
const imageContainer = ref(null)
-> You set the value to null
and TypeScript warns you that any access to the object's properties may throw an error, Because the initial value is null
and because you are trying to use ref
in the template the element may or may not be present.
const imageContainer = document.querySelector('.imageContainer')
-> You are querying for the presence or absence of HTML elements, which means you can also get null
as a value, and TypeScript again warns you that any access to the object's properties may throw an error
solution:
setup
variable from the setup
function so that Vue will bind it to the ref
in the template and can be used in onMounted
The variable is accessed inside the function because it is a ref
, which means it has not been mounted into the DOM. setup
is called before the created
and mounted
hooks in the Vue.js component lifecycle, and you do not have access to any DOM content within them. import { ref, onMounted } from 'vue' export default { setup() { const imageContainer = ref(null) onMounted(() => { // here you access imageContainer variable }) return { imageContainer } } }