使用ResizeObserver製作響應式Vue元件
前言
- 一提到製作響應式元件或佈局,腦海裡首先想到的是通過@media查詢來控制,但是有一個問題,它能滿足你的需求麼?大多數情況下可以很好的解決問題,有時也會不靈驗。已一個例子作為說明。
- 假設你要建立一個postItem元件,在大屏上post是這樣的顯示效果
- 在手機上我需要這樣的效果
- 第一反應就是想到媒體查詢,根據頁面的寬度來控制樣式,於是就有了下面的樣式。
@media only screen and (max-width: 576px) {
.post__item {
flex-direction: column;
}
.post__image {
flex: 0 auto;
height: auto;
}
}
複製程式碼
- 元件具有重用性,不是一次買賣,某天需求變了要在一個頁面上根據post的類別來顯示,效果如下
- @media查詢的最大問題是,元件響應性基於螢幕大小,但應基於其自身大小,這時原來的媒體查詢就不靈驗了。在這種情況下,元件佈局僅取決於它們。這些元件應該是原子的,獨立地確定它們自己的大小並使其適應佈局。構建響應式元件,ResizeObserver是個不錯的選則。
介紹ResizeObserver
- ResizeObserver:是一項新的功能,監聽元素的內容矩形大小的變更,並通知做出相應的反應。和document.onresize的功能很相似。
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const cr = entry.contentRect;
console.log('Element:', entry.target);
console.log(`Element size: ${cr.width}px x ${cr.height}px`);
console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
})
})
observer.observe(someElement)
複製程式碼
- 瀏覽器的支援性,如圖所示
- 雖然目前主流瀏覽器對ResizeObserver不支援,慶幸的是,ResizeObserver有基於MutationObserver的polyfill,而主流瀏覽器對MutationObserv是支援的
使用
<template>
<Responsive :breakpoints="{
small: el => el.width <= 500
}">
<div slot-scope="el" :class="['post__item', { small: el.is.small }]">
<img class="post__image" :src="post.image" />
<div class="post__text">{{post.text}}</div>
</div>
</Responsive>
</template>
<script>
import { Responsive } from "vue-responsive-components"
export default {
props: ['post'],
components: { Responsive }
}
</script>
<style lang="scss">
.post__item {
display: flex;
}
.post__image {
flex: 0 0 200px;
height: 200px;
}
.post__item.small {
flex-direction: column;
.post__image {
flex: 0 auto;
height: auto;
}
}
</style>
複製程式碼
<template>
<!-- Will add/remove .small if the width is less / greater -->
<div class="post__item" v-responsive="{ small: el => el.width <= 500 }">
<img class="post__image" :src="post.image" />
<div class="post__text">{{post.text}}</div>
</div>
</template>
<script>
import { ResponsiveDirective } from "vue-responsive-components"
export default {
props: ["post"],
directives: {
responsive: ResponsiveDirective
}
}
</script>
複製程式碼
外掛程式碼實現
- npm install resize-observer-polyfill --save-dev
- npm install loadsh --save-dev
import throttle from "lodash.throttle"
import ResizeObserver from "resize-observer-polyfill"
export const ResponsiveMixin = {
data() {
return {
el: {
width: 0,
height: 0,
is: {}
}
}
},
mounted() {
if (
typeof process === "undefined" ||
(!process.server && (this.breakpoints || this.$options.breakpoints))
) {
this.$nextTick(() => {
const handleResize = throttle(entries => {
const cr = entries[0].contentRect
;(this.el.width = cr.width), (this.el.height = cr.height)
const conds = Object.assign(
{},
this.breakpoints || {},
this.$options.breakpoints || {}
)
for (const breakpoint in conds) {
this.$set(this.el.is, breakpoint, conds[breakpoint](this.el))
}
}, 200)
const observer = new ResizeObserver(handleResize)
if (this.$el instanceof Element) {
observer.observe(this.$el)
}
})
}
}
}
export const Responsive = {
data() {
return { init: false }
},
props: {
noHide: { type: Boolean, default: false },
breakpoints: { type: Object, default: undefined }
},
mixins: [ResponsiveMixin],
render(h) {
const slot =
(this.$scopedSlots.default && this.$scopedSlots.default(this.el)) ||
this.$slots.default
return !this.noHide && !this.init
? h(
"div",
{
style: { visibility: "hidden" }
},
[slot]
)
: slot
},
mounted() {
this.init = true
}
}
export const ResponsiveDirective = {
inserted(el, conds) {
if (typeof process === "undefined" || !process.server) {
const handleResize = throttle(entries => {
const cr = entries[0].contentRect
for (const breakpoint in conds.value) {
if (conds.value[breakpoint](cr)) {
el.classList.add(breakpoint)
} else {
el.classList.remove(breakpoint)
}
}
}, 200)
const observer = new ResizeObserver(handleResize)
observer.observe(el)
}
}
}
export const VueResponsiveComponents = Vue => {
Vue.component("Responsive", Responsive)
Vue.directive("responsive", ResponsiveDirective)
}
複製程式碼
總結
- ResizeObserver對響應式佈局提供了一種新穎的解決思路,讓元件保持了原子性、獨立性,同樣的思路也適用於angular、react等元件寫法(注意需根據angular、react語法規則更改)。
參考文件