I am developing a Vue.js web application that involves a photo upload feature. It works perfectly on desktop browsers, but I am encountering a critical issue on Mobile Chrome (Android).
The Symptom: When I open the camera (using capture="camera") inside a modal dialog, take a photo, and return to the app, the dialog immediately closes or the entire browser page crashes/reloads. This only happens when taking a photo; selecting an existing file usually works fine.
What I have implemented: I am using URL.createObjectURL for image previews and ensuring that URL.revokeObjectURL is called in the onUnmounted hook to prevent memory leaks. I also have a custom resizeImage function that runs before setting the state.
Here is the relevant code snippet:
<div class="photo-section">
<div v-for="type in photoTypes" :key="type.value" class="photo-item">
<div class="photo-header">
<span class="photo-label">{{ type.label }}</span>
<div class="button-group">
<label :for="'camera-' + type.value">
<span class="material-symbols-outlined" style="pointer-events: none;">photo_camera</span>
</label>
<input
type="file"
:id="'camera-' + type.value"
accept="image/*"
capture="camera"
style="display: none;"
@change="onPhotoChange($event, type.value)"
/>
<label :for="'file-' + type.value">
<span class="material-symbols-outlined" style="pointer-events: none;">file_open</span>
</label>
<input
type="file"
:id="'file-' + type.value"
accept="image/*"
style="display: none;"
@change="onPhotoChange($event, type.value)"
/>
</div>
</div>
<div class="photo-window" @click="openZoom(type.value)" style="cursor: pointer;">
<span v-if="!readingData.photos?.[type.value] && !photoPreviews[type.value]">등록 필요</span>
<img v-else :src="photoPreviews[type.value] || readingData.photos[type.value]" alt="미리보기" />
</div>
</div>
</div>
..................
const onPhotoChange = async (event, photoType) => {
try {
const file = event.target.files[0];
if (file) {
try {
const resizedFile = await resizeImage(file); // Utility function
readingData.photos[photoType] = resizedFile;
if (photoPreviews[photoType]?.startsWith('blob:')) {
URL.revokeObjectURL(photoPreviews[photoType]);
}
photoPreviews[photoType] = URL.createObjectURL(resizedFile);
photoDirtyFlags[photoType] = true;
} catch (error) {
console.error("Resizing error:", error);
}
}
} catch (error) {
alert(error);
} finally {
event.target.value = '';
}
};
// Cleanup in onUnmounted
onUnmounted(() => {
['FAR', 'NEAR', 'ETC'].forEach(type => {
if (photoPreviews[type]?.startsWith('blob:')) {
URL.revokeObjectURL(photoPreviews[type]);
}
});
});