There is something like the window's onresize
event listener but for DOM elements. It's called ResizeObserver and makes measuring dynamic elements very easy. The only problem is it's not standard. ๐ It's been in the editor's draft list for years. Almost all browsers support it. Safari started supporting it since 13.1 but Internet Explorer and Firefox android still don't support it. So... is there another way to measure a changing element other than a setInterval
? Enter MutationObserver.
Here's a demo of the code that will be explained below:
The <template>
or HTML part
<template>
<div class="resize-observer">
<div class="box" ref="box">
<h4>Resize Me</h4>
<p>
width: <span class="size">{{ width }}</span>
</p>
<p>
height: <span class="size">{{ height }}</span>
</p>
</div>
</div>
</template>
We'll be resizing the div
element with class "box" by giving it a css property resize: both
. This will give the rendered box a small triangle on the bottom-right corner that allows for resizing.
Since we'll need access to the actual HTML element and its changing dimensions, we'll be using a ref
. You can read more about refs here.
The <script>
Code for Resizing with MutationObserver
<script>
export default {
data() {
return {
width: null,
height: null,
observer: null,
}
},
mounted() {
const box = this.$refs.box,
boxSize = box.getBoundingClientRect()
this.width = boxSize.width + 'px'
this.height = boxSize.height + 'px'
// initialize the observer on mount
this.initObserver()
},
We'll add the width
, height
and the observer
to our state. Then when the component is mounted, we'll set the width and height. We'll also initialize the observer by calling this.initObserver
.
beforeDestroy() {
if (this.observer) this.observer.disconnect()
},
We also need to make sure we disconnect the observer before destroy.
We've now reached the main part of this tutorial. In the initObserver
method, we create and initialize an observer which is an instance of MutationObserver
. This observer tracks our box element's mutations and calls another method - onResize
when the mutation type is attributes (the box's width and height attributes will fall under this). onResize
is our resize handler.
methods: {
initObserver() {
const config = {
attributes: true,
},
vm = this
// create the observer
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// check if the mutation is attributes and
// update the width and height data if it is.
if (mutation.type === 'attributes') {
// call resize handler on mutation
vm.onResize()
}
})
})
// observe element's specified mutations
observer.observe(this.$refs.box, config)
// add the observer to data so we can disconnect it later
this.observer = observer
},
The resize handler updates the state when the dimensions change. Optionally, you can emit an event that other components can listen to. More info on emitting events with vue.
// Resize handler
onResize() {
const box = this.$refs.box,
vm = this
let { width, height } = box.style
this.width = width
this.height = height
// Optionally, emit event with dimensions
this.$emit('resize', { width, height })
},
},
}
</script>
The <script>
code for Resizing with ResizeObserver
Here's how you would do it with ResizeObserver
. You'll notice that it's a lot less code when you implement with ResizeObserver
. The <template>
and <style>
portion will stay the same.
<script>
export default {
data() {
return {
width: null,
height: null,
observer: null,
}
},
mounted() {
// initialize the observer on mount
this.initObserver()
},
// unobserve before destroy
beforeDestroy() {
if (this.observer) this.observer.unobserve(this.$refs.box)
},
methods: {
onResize() {
const box = this.$refs.box,
width = this.$refs.box.offsetWidth + 'px',
height = this.$refs.box.offsetHeight + 'px'
this.width = width
this.height = height
this.$emit('resize', { width, height })
},
initObserver() {
const observer = new ResizeObserver(this.onResize)
observer.observe(this.$refs.box)
this.observer = observer
},
},
}
</script>
And here's the full code with <template>
, <script>
and <style>
for resizing with MutationObserver
.
<template>
<div class="resize-observer">
<div class="box" ref="box">
<h4>Resize Me</h4>
<p>
width: <span class="size">{{ width }}</span>
</p>
<p>
height: <span class="size">{{ height }}</span>
</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
width: null,
height: null,
observer: null,
}
},
mounted() {
const box = this.$refs.box,
boxSize = box.getBoundingClientRect()
this.width = boxSize.width + 'px'
this.height = boxSize.height + 'px'
// initialize the observer on mount
this.initObserver()
},
//disconnect the observer before destroy
beforeDestroy() {
if (this.observer) this.observer.disconnect()
},
methods: {
// Resize handler
onResize() {
const box = this.$refs.box,
vm = this
let { width, height } = box.style
this.width = width
this.height = height
// Optionally, emit event with dimensions
this.$emit('resize', { width, height })
},
initObserver() {
const config = {
attributes: true,
},
vm = this
// create the observer
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// check if the mutation is attributes and
// update the width and height data if it is.
if (mutation.type === 'attributes') {
// call resize handler on mutation
vm.onResize()
}
})
})
// observe element's specified mutations
observer.observe(this.$refs.box, config)
// add the observer to data so we can disconnect it later
this.observer = observer
},
},
}
</script>
<style lang="scss" scoped>
.resize-observer {
text-align: center;
h4 {
margin-top: 30px;
text-align: center;
}
.box {
box-sizing: border-box;
width: 210px;
height: 210px;
border: 2px solid red;
padding: 10px;
margin: 0 auto;
resize: both;
overflow: auto;
}
.size {
color: #2a9966;
font-weight: 600;
}
}
</style>
Top comments (2)
MutationObserver doesn't work
It works only if you change property width, but otherwise if width is set to auto or 100% and someone change the size of the screen then mo won't observe those changes and won't trigger
Great article, it helped to resolve an issue that I have. Thanks for the contribution.
Just a comment, if you are going to use the ResizeObserver you should use the polyfill, resize-observer-polyfill (1) because not all modern browser support the ResizeObserver yet. After adding the polyfill, it should work in all the browser.
(1): npmjs.com/package/resize-observer-...