Membuat Komponen untuk Mengecek Viewport di Vue
Saya menjelaskan bagaimana cara mengecek viewport dengan komponen untuk mendapatkan keuntungan reusabilitas dan penggunaan secara deklaratif pada template
Kadang-kadang kita perlu mengecek viewport melalui JS untuk melengkapi media query CSS untuk membuat sebuah desain web yang responsif. Pada artikel ini, saya akan menjelaskan bagaimana cara mengimplementasikannya sebagai suatu komponen untuk mendapatkan keuntungan reusabilitas dan penggunaan secara deklaratif pada template.
Mendesain API-nya
Misalkan kita hanya ingin menggunakan nilai viewport dari slot scope. Di bawah ini adalah ide kasar bagaimana kita akan menggunakannya di template.
<template>
<Viewport>
<template v-slot:default="{ value }">
<div v-if="value > 1280">
<!-- konten yang tampil -->
</div>
<div v-else>
<!-- konten default -->
</div>
</template>
</Viewport>
</template>
Pada komponen ini, kita hanya menggunakan slot default untuk menyediakan nilai viewport. Properti value
perlu diubah setiap kali pengguna melakukan resize pada layar browser. Oleh karena itu, kita akan membutuhkan listener untuk event resize untuk mengupdate nilai viewport di komponen.
Implementasi
Kita akan membuat sebuah state untuk menyimpan nilai viewport dan mengembalikannya sebagai slot prop. Kodenya akan terlihat seperti di bawah ini:
<script>
export default {
data() {
return {
value: 0,
};
},
render() {
// mengembalikan state sebagai slot props
return this.$scopedSlots.default({
value: this.value,
});
},
};
</script>
Sekarang kita perlu mengeset nilai awal dari state pada lifecycle mounted
.
<script>
export default {
data() {
return {
value: 0,
};
},
// set nilai awal 'value'
mounted() {
this.value = window.innerWidth;
},
// ...
};
</script>
Karena kita ingin state value
untuk berubah setiap saat pengguna melakukan resize, kita akan mengeset sebuah event listener seperti berikut ini.
<script>
export default {
data() {
return {
value: 0,
};
},
mounted() {
// kita membuat sebuah method untuk bagian ini
// supaya kita bisa menggunakannya kembali nanti di dalam listener
this.setValue();
this.setListener();
},
methods: {
setValue() {
this.value = window.innerWidth;
},
setListener() {
let timeout;
// karena event resize bisa menjadi proses yang cukup intensif
// kita menggunakan requestAnimationFrame supaya tidak menghalangi proses rendering browser
window.addEventListener("resize", () => {
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => {
this.setValue();
});
});
},
},
// ...
};
</script>
Dan selesai!
Menambahkan fungsi breakpoint
Daripada mengecek secara manual nilai viewport dari komponen, kita juga bisa menambahkan fungsi breakpoint di komponen supaya kita tidak perlu menambahkan nilai-nilai aneh atau mengimpor konfigurasi breakpoint dari framework CSS kita ke template. Penggunaannya akan terlihat seperti di bawah ini:
<template>
<Viewport>
<template v-slot:default="{ breakpoint }">
<div v-if="breakpoint === 'lg'">
<!-- konten untuk ditampilkan pada layar yang lebih lebar -->
</div>
<div v-else>
<!-- konten untuk ditampilkan pada layar yang lebih kecil -->
</div>
</template>
</Viewport>
</template>
Untuk menambahkan fungsi breakpoint, kita perlu memperkenalkan state baru dengan nama breakpoint
, lalu state tersebut akan berubah tergantung kepada state value
. Kita akan memanfaatkan watcher untuk men-track state value
dan menjalankan sebuah method untuk mengubah state breakpoint
.
<script>
export default {
data() {
return {
value: 0,
// tambah state baru
breakpoint: "",
};
},
// menambahkan watcher untuk state 'value'
watch: {
value: {
// perlu mengeset immediate sebagai 'true' untuk menjalankan handler
// segera setelah observasi state-nya dimulai
immediate: true,
handler: "setBreakpoint",
},
},
// ...
methods: {
// ...
// Saya menggunakan aturan small-to-large disini
// tapi kamu bisa mengubah logic-nya sesuai keperluan
setBreakpoint(value) {
let breakpoint = "xs";
if (value >= 576) {
breakpoint = "sm";
} else if (value >= 756) {
breakpoint = "md";
} else if (value >= 1024) {
breakpoint = "lg";
} else if (value >= 1280) {
breakpoint = "xl";
}
this.breakpoint = breakpoint;
},
},
render() {
return this.$scopedSlots.default({
value: this.value,
// mengembalikan state 'breakpoint' sebagai slot props
breakpoint: this.breakpoint,
});
},
};
</script>
Akhirnya, versi komplit dari kodenya akan terlihat seperti ini.
<script>
export default {
data() {
return {
value: 0,
breakpoint: "",
};
},
watch: {
value: {
immediate: true,
handler: "setBreakpoint",
},
},
mounted() {
this.setValue();
this.setListener();
},
methods: {
setValue() {
this.value = window.innerWidth;
},
setListener() {
let timeout;
window.addEventListener("resize", () => {
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => {
this.setValue();
});
});
},
setBreakpoint(value) {
let breakpoint = "xs";
if (value >= 576) {
breakpoint = "sm";
} else if (value >= 756) {
breakpoint = "md";
} else if (value >= 1024) {
breakpoint = "lg";
} else if (value >= 1280) {
breakpoint = "xl";
}
this.breakpoint = breakpoint;
},
},
render() {
return this.$scopedSlots.default({
value: this.value,
breakpoint: this.breakpoint,
});
},
};
</script>
Selesai! Semoga bermanfaat!