Home

I encountered an interesting issue last week, as I was setting up apex charts within a project that uses Vuetify. On page load, the layout would shift because Vuetify adjusts the container width to account for the navigation drawer. This layout shift caused the charts to render larger than the parent it was contained in.

One hacky way to solve this is to re-render the chart after some time using a setTimeout() in the Vue onMounted hook. This is obviously not desirable and does not guarantee the render occurs reliably, unless you set an unreasonable wait time for the timeout.

After some digging, I came to the conclusion that Vuetify does not provide a watchable event that tracks the resizing that occurs. So I decided to implement my own solution. Here’s how my layout file looked.

				
					<template>
    <v-app>
      <v-main class="main">
        <v-container ref="container" class="position-relative">
          <v-navigation-drawer disable-resize-watcher :style="{ zIndex: 1005 }" v-model="drawer">
            <v-list nav>
            </v-list>
          </v-navigation-drawer>
          <div class="px-3 py-10">
            <slot></slot>
          </div>
        </v-container>
      </v-main>
  </v-app>
 </template>
				
			
				
					<script setup lang="ts">
import { useLayoutStore } from "../store;
import { throttle } from "lodash";

//Template ref for the <v-container>
const container = ref();
const layout = useLayoutStore();
const drawer = ref(true);
let resizeObserver: ResizeObserver | null;

/*
Registers an observer to observe size changes 
in the <v-container> element. Uses the throttle function 
from lodash to reduce unnecessary calls 
*/
function RegisterObserver() {
  if (!resizeObserver) {
    resizeObserver = new ResizeObserver(throttle((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        //Set's the containerDimensions value in my pinia layout store
        layout.containerDimensions = { width, height };
      }
    }, 250));
  }
  resizeObserver.observe(container.value?.$el as HTMLElement);
}

function UnregisterObserver() {
  if (resizeObserver) {
    resizeObserver.disconnect();
    resizeObserver = null;
  }
}

onMounted(() => {
/*
Registering on nextTick() is important as vuetify is only fully initialized 
after the nextTick
*/
  nextTick(() => {
    RegisterObserver();
  })
});

/*
It's necessary to unregister, otherwise client side navigation 
won't destroy the resize observer
*/
onBeforeUnmount(() => {
  UnregisterObserver();
});
</script>
				
			

Here’s what the above code is doing:

  • When the component is mounted, RegisterObserver() function is called inside the nextTick() callback
  • Then an observer is registered on the container template ref.
  • When the container is resized, the containerDimensions ref is updated in the layout store (explained further down).
  • The ResizeOberserver registers a callback handler, and I’ve opted to use the throttle function in lodash. This is to reduce overheads of calling it every update in the event loop.
  • The final step is to unregister the observer. With client side navigation, navigating away won’t destroy the observer. This will cause errors or multiple observers being registered over time, causing performance issues.

In my case, this code was being used inside a layout file. And I wanted to be able to listen to the container resize changes from any page using the layout. This is why I built a pinia store. All I have to do, then, is to import this store into pages and register a ref watch().

Here’s how the store looked:

				
					// store/index.ts
export const useLayoutStore = defineStore({
    id: 'layoutStore',
    state: () => ({
        containerDimensions: {width: 0, height: 0},
    }),
});
				
			

Now watch for changes in any component or page.

				
					<script setup lang="ts">
import { useLayoutStore } from '~/store';

const revChart = ref<any | null>(null);
const layout = useLayoutStore();

/* Watches for changes to containerDimensions 
and refeshes the chart graphic
*/
watch(async () => layout.containerDimensions, (val) => {
    revChart.value?.refresh();
});
</script>