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"
This is the reason for this error:
const imageContainer = ref(null)
-> You set the value tonull
and TypeScript warns you that any access to the object's properties may throw an error, Because the initial value isnull
and because you are trying to useref
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 getnull
as a value, and TypeScript again warns you that any access to the object's properties may throw an errorsolution:
setup
variable from thesetup
function so that Vue will bind it to theref
in the template and can be used inonMounted
The variable is accessed inside the function because it is aref
, which means it has not been mounted into the DOM.setup
is called before thecreated
andmounted
hooks in the Vue.js component lifecycle, and you do not have access to any DOM content within them.