What is causing this picture to generate two network requests when the button is clicked?

Utilizing an open-source web-based viewer within a Vue3 application, I have achieved success in displaying the image only upon clicking the "Open Image" button.

Nevertheless, is there anyone who can clarify why two network requests are being made for the same image when the "Open Image" button is triggered?

https://i.sstatic.net/7GLP3.png

Below is my simplified reproduction:

sandbox: https://stackblitz.com/edit/vitejs-vite-xxxk9w?file=src/App.vue

App.vue:

<script setup>
import { ref } from 'vue';
import Viewer from './components/Viewer.vue';
const show = ref(false);
</script>

<template>
  <div>
    <button type="button" @click="show = true">Open Image</button>
    <Viewer v-if="show" />
  </div>
</template>

Viewer.vue:

<template>
  <div ref="osdContainer" style="width: 500px; height: 500px"></div>
</template>

<script setup>
import OpenSeadragon from 'openseadragon';
import { ref, onMounted } from 'vue';
const viewer = ref(null);
const osdContainer = ref(null);

const initViewer = () => {
  console.log('init Viewer');
  viewer.value = OpenSeadragon({
    element: osdContainer.value,
    tileSources: {
      type: 'image',
      url: 'https://ik.imagekit.io/pixstery/users%2F5cnu6iDlTsa5mujH2sKPsBJ8OKH2%2Fposts%2Fportrait-of-arabian-man_jjC2?alt=media&token=64fb0ae4-b0dc-4ead-b22e-292e55de1447&tr=f-auto,pr-true,q-80',
      buildPyramid: false,
    },
  });
};

onMounted(() => {
  console.log('mounting..');
  initViewer();
});
</script>

Answer №1

OpenSeadragon operates using tiled image pyramids, where image metadata (such as resolution) and the actual tiles (bitmap data) are usually accessed separately.

In this setup, supporting individual images is not the norm, and it is still treated as if the image metadata and bitmap data come from different sources.

The first request originates from getImageInfo() in the specialized class ImageTileSource, which handles support for images:

        var image = this._image = new Image();

        [...]

        $.addEvent(image, 'load', function () {
            _this.width = image.naturalWidth;
            _this.height = image.naturalHeight;
            _this.aspectRatio = _this.width / _this.height;
            _this.dimensions = new $.Point(_this.width, _this.height);
            _this._tileWidth = _this.width;
            _this._tileHeight = _this.height;
            _this.tileOverlap = 0;
            _this.minLevel = 0;
            _this.levels = _this._buildLevels();
            _this.maxLevel = _this.levels.length - 1;

            _this.ready = true;

            // Note: this event is documented elsewhere, in TileSource
            _this.raiseEvent('ready', {tileSource: _this});
        });

        [...]

        image.src = url;             // <----------

The second request is made when fetching bitmap data in _loadTile():

      _loadTile: function(tile, time) {
        var _this = this;
        tile.loading = true;
        this._imageLoader.addJob({
          src: tile.getUrl(),           // <-------

This section of code is generic to all instances, such as TiledImage. This highlights a limitation of current OpenSeadragon: the generic code requests a URL instead of direct tile data, even though the ImageTileSource already holds the complete image in its _image field.

getTileUrl() in TileImageSource straightforwardly provides the necessary URL:

        var url = null;
        if (level >= this.minLevel && level <= this.maxLevel) {
            url = this.levels[level].url;
        }
        return url;

Referring to "magic," one could consider utilizing createObjectURL(), enabling image download via fetch(), transformation with blob(), and ultimately using the resulting URL for both image.src = and returning in getTileUrl().


(*) And why this likely won't affect performance:

  • Browsers utilize caching mechanisms, so multiple requests may only occur due to specific settings like "Disable cache (while DevTools is open)." Enabling browser cache often results in a single request.
  • Efforts are underway, as seen in discussions like https://github.com/openseadragon/openseadragon/pull/2148 and improvements in the original getTileUrl(). The groundwork has been laid in ImageTileSource; it's just awaiting full implementation.

Answer №2

Two network requests are being generated for the same image when the "Open Image" button is clicked because the initViewer() function is executed each time the viewer.vue component is mounted or re-rendered.

Here's what occurs upon clicking the "Open Image" button:

  1. The Viewer component is rendered and mounted.
  2. This triggers the onMounted hook, which in turn calls the initViewer() function.
  3. Subsequently, the first network request is sent to load the image. When the show value changes to false (e.g., during unmounting or re-rendering), the Viewer component gets destroyed. Upon setting the show value back to true, the Viewer component is re-rendered, triggering the initViewer() function again and generating another network request to load the image.

To eliminate this issue, you can update your code to call the initViewer function only once by introducing an isViewerInitialized ref variable to track whether the viewer has been initialized. Check its value before invoking initViewer.

UPDATE: Apologies for the confusion earlier. The behavior you're experiencing is mainly due to OpenSeadragon's design of loading images separately via HTTP requests for lazy loading purposes.

When you click the "Open Image" button, the viewer component loads, and the OpenSeadragon viewer initializes. Initially, the viewer lacks image data, prompting a separate HTTP request to fetch the image data from the server.

If you wish to bypass this behavior and load the image data directly upon viewer initialization, set the immediateRender option to true. This will prompt OpenSeadragon to load the image data immediately upon viewer initialization instead of waiting until it's required.

Below is how you can adjust your code to implement the immediateRender option:

Source:

<script setup>
import OpenSeadragon from 'openseadragon';
import { ref, onMounted } from 'vue';

const viewer = ref(null);
const osdContainer = ref(null);
const isViewerInitialized = ref(false);

const initViewer = () => {
  console.log('init Viewer..');
  viewer.value = OpenSeadragon({
    element: osdContainer.value,
    tileSources: {
      type: 'image',
      url: 'https://ik.imagekit.io/pixstery/users%2F5cnu6iDlTsa5mujH2sKPsBJ8OKH2%2Fposts%2Fportrait-of-arabian-man_jjC2?alt=media&token=64fb0ae4-b0dc-4ead-b22e-292e55de1447&tr=f-auto,pr-true,q-80',
      buildPyramid: false,
      immediateRender: true
    },
  });
  isViewerInitialized.value = true;
};

onMounted(() => {
  if (!isViewerInitialized.value) {
    initViewer();
  }
});
</script>

In my opinion, having two requests and maintaining immediateRender: false isn't necessarily bad. It contributes to better user experience by facilitating lazy loading.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transferring JavaScript code from Node Express to an Angular service

I am currently working on creating a unified JavaScript endpoint for utility methods that can be used on both the client and server sides. Despite conducting research, I am finding it difficult to fully understand the concepts involved. I have been consi ...

Combine two arrays and collapse one of the nested elements

I need help merging two arrays based on ID and flattening one of the objects. I haven't been able to find a solution for my specific case. This is what I'm attempting and what I've managed to achieve so far: const a = { someProperty: &a ...

Converting a momentjs datetime stored in MySQL in UTC format to local DateTime format

I have a column in my table named "registerdate" with the data type "datetime" in MySQL. Let's assume the current time is "2015-10-10 06:00:00" in local time. In the database, I am storing UTC time, so it will be converted to "2015-10-10 00:30:00" a ...

Exploring Nuxt's speed and efficiency with Lighthouse

Despite having simple code, I am puzzled by the low performance of my Nuxt web app in Lighthouse. You can check out my Nuxt web app deployed under the name hadicodes.com online. Here is a snippet from my nuxt.config: // Your nuxt.config settings here Th ...

Tips for accessing raw POST data in PHP using $_SERVER

I am facing a challenge with my dynamic page where I use on-page filters that modify the data through AJAX response. My concern is how to access raw POST data when it's not visible in the URL... The link on my page appears as "example.com/default/lis ...

What is the solution for the error message "this.filters is not a function" in Vue.js 2?

Here is the code for my component: <script> import _ from 'lodash' export default{ props:['search','category'], data(){ return{ price_min:'', ...

Ionic 2: Unveiling the Flipclock Component

Can anyone provide guidance on integrating the Flipclock 24-hours feature into my Ionic 2 application? I'm unsure about the compatibility of the JavaScript library with Ionic 2 in typescript. I have searched for information on using Flipclock in Ionic ...

Concluding the use of angular-bootstrap-datetimepicker when selecting a date

I am currently utilizing the UI Bootstrap drop-down component to display the angular-bootstrap-datetimepicker calendar upon clicking. Although it works well for the most part, I have encountered an issue where the calendar block does not close when clicked ...

Setting the default selected option in Vue for an object

Can the v-model data be different from the default option in this scenario? data: function() { return { user: { usertype: {}, } } <select v-model="user.usertype" class="btn btn-secondary d-flex header-input"> <option v-for= ...

Error encountered in the identify() method of Microsoft Face API

While utilizing Microsoft Face API with project oxford in JavaScript, I encountered an issue when using the "identify" function resulting in an error message of "Invalid request body." client.face.identify({ faces: arrayFaceId, personGroupId: "groupId ...

Ways to retrieve the value of a concealed element without any activation signal

In my project, I've created a slider that dynamically shows different forms one by one. Each form contains a hidden element with a specific value that I need to retrieve (using its class) every time a new form is displayed. This way, I can use that el ...

Updating a deeply nested value with React's setState

Dealing with a deeply nested object in my React state has been quite the challenge. The task at hand is to modify a value within a child node. Fortunately, I have already identified the path leading to the node that needs updating and I am utilizing helper ...

Tips on dynamically passing parameters in Angular's $resource

Is there a way to dynamically pass a default parameter value in $resource? Take a look at the Plunker for reference. In the code snippet below, I've set the default parameter in the factory as: app.factory('myFactory', ["$resource", functio ...

Updating content on a webpage via AJAX request without altering the original source code

Within the body of our document, referred to as "form.php," we have the following components: At the head section, there is a JavaScript code block: <script> function showUser(str) { if (str == "") { document.getElementById("txtHint").i ...

Send data via AJAX requests to store in the database

I am currently working on a project to develop a platform where users can add scenes to YouTube videos by providing the scene name and start & pause time. I have already created an interface that allows users to input this information, but I am now lookin ...

The React useEffect hook runs whenever there is a change in the state

Within my React component, I have the following code snippet (excluding the return statement for relevance): const App = () => { let webSocket = new WebSocket(WS_URL); const [image, setImage] = useState({}); const [bannerName, setBannerName] = use ...

Launch a PowerPoint presentation in a separate tab on your web browser

If you have a link that leads to a .pdf file, it will open in a new tab like this: <a target="_blank" href="http://somePDF.pdf">Download</a> But what about a Powerpoint file, such as a .ppt? Is there a way to make it open in a new browser tab ...

The issue with Angular JS is that it fails to refresh the select and input fields after selecting a date

Currently utilizing Angular JS and Bootstrap, I have a query regarding updating the input for a datepicker calendar and a select from a function. The values are being successfully updated, however, they do not reflect on their respective inputs. It seems ...

Configuring the IntelliJ debugger to allow for breaking inside or stepping through an NPM script

I am utilizing an NPM script that executes and produces a file within the repository. Could you please guide me on setting up the debugger to run the script in a way that allows me to perform actions like breaking inside of it and stepping through its cod ...

Storing the information filled out in the form and utilizing it to navigate to the correct destination URL

With the generous assistance and insightful advice from members of Stack Overflow, I am nearing completion of my quiz project. However, I do have a few lingering questions regarding some final touches needed for the project. Before delving into those quest ...