Nuxt.js: add custom loading component - vue.js

I have been trying to add loading spinner to my NuxtJS project using the loading configuration: https://nuxtjs.org/api/configuration-loading.
However, the documentation is hard to understand, I have no idea how to apply it to my project. Did any have any ideas of how to add loading spinner using that?
Thanks in advance,

So you can create a custom loading:
We can create our custom component in components/loading.vue:
<template lang="html">
<div class="loading-page" v-if="loading">
<p>Loading...</p>
</div>
</template>
<script>
export default {
data: () => ({
loading: false
}),
methods: {
start () {
this.loading = true
},
finish () {
this.loading = false
}
}
}
</script>
<style scoped>
.loading-page {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
text-align: center;
padding-top: 200px;
font-size: 30px;
font-family: sans-serif;
}
</style>
Then, we update our nuxt.config.js to tell Nuxt.js to use our component:
export default {
loading: '~/components/loading.vue'
}
And then in your compenent you can show and hide with the:
this.$nuxt.$loading.start() to start the loading bar and this.$nuxt.$loading.finish() to finish it.
You can put this in the callbacks of a request.

probably it's about z-index value

Related

How can i change style of the body when my modal it will be open?

How can i change the body{overflow:hidden} when my modal it will be open?
for example it will be my modal, when its open, i would like to apply this style body{overflow:hidden}
<div v-if="dialogFoundation">
i am using vuejs3, i am using setup(){...}
The best performance would be to use javascript plain. You can add Eventlistener top the modal trigger Element. In my example i use a button. If it triggered then you can use classList and assign the body a class. In my example .dark.
Vue version
<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
<div id="app">
<h1>{{message}}</h1>
<p></p>
<button #click="doSomething">Modal</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Welcome to Vue!'
};
},
methods: {
doSomething() {
const b = document.querySelector('body');
b.classList.toggle('dark');
}
}
};
</script>
<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
a,
button {
color: #4fc08d;
}
button {
background: none;
border: solid 1px;
border-radius: 2em;
font: inherit;
padding: 0.75em 2em;
}
.dark {
background: black;
opacity: 0.4;
}
</style>
Vanilla JS
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
const b = document.querySelector('body');
b.classList.toggle('dark');
})
.dark {
background: black;
opacity: 0.4;
}
<body>
<div></div>
<button>click</button>
</body>
You can use watchers in Vue.js for solving this problem.
When variables changes you can check whether it is true or not, and if true change overflow of body to hidden.
{
watch: {
dialogFoundation(dialogFoundation) {
document.body.style.overflow = dialogFoundation ? "hidden" : "auto"
}
}
}
But I think this is not good solution. You can set this styles to your app element
#app {
width: 100%;
height: 100%;
overflow: auto;
}
and you can change style of app element using Vue directives.
<template>
<div id="app" :class="{ hidden: dialogFoundation }">
Long text....
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const dialogFoundation = ref(true);
return { dialogFoundation };
},
};
</script>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
overflow: auto;
}
#app.hidden {
overflow: hidden;
}
</style>
Code in codesandbox - https://codesandbox.io/s/immutable-glitter-rwc2iy?file=/src/App.vue

How to display a component when page is loading in nuxt

I am quite new to nuxt, and I need help here.
async asyncData({ params, route }) {
const { data } = await axios.get(
`${process.env.baseUrl}/homes/?search=${
params.search
}&home_status=${1}`
)
return {
homes: data.results,
}
}
I am trying to populate my component with data(using asyncData), but I want my skeleton loader to show if my page is loading. How do I do that in nuxt?
Here is the code for my skeleton loader;
<template>
<div class="placeholder-container">
<div class="placeholder wave">
<div class="square"></div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
</div>
</div>
</template>
<style scoped>
.placeholder-container {
width: 35rem;
margin: 15px auto 15px auto;
}
.placeholder {
padding: 10px;
width: 100%;
// border: 1px solid lightgrey;
display: flex;
flex-direction: column;
}
.placeholder div {
background: #e8e8e8;
}
.placeholder .square {
width: 100%;
height: 22rem;
border-radius: 1rem;
margin: 0 0 10px;
}
.placeholder .line {
height: 12px;
margin: 0 0 10px 0;
}
.placeholder .line:nth-child(2) {
width: 120px;
}
.placeholder .line:nth-child(3) {
width: 180px;
}
.placeholder .line:nth-child(4) {
width: 150px;
}
.placeholder.wave div {
animation: wave 1s infinite linear forwards;
-webkit-animation: wave 1s infinite linear forwards;
background: #f6f7f8;
background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
background-size: 800px 104px;
}
#keyframes wave {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
#-webkit-keyframes wave {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
</style>
What I normally do without using nuxt, is to create a data variable(loading=true), and change it to false after I finish making the api call, but since asyncData runs in the server, how do I make that work? I will also appreciate it if there is a better way of doing something like this
Placeholder
To display a placeholder component on a particular page
during loading, switch from asyncData to the fetch hook, which exposes the $fetchState.pending flag that is set to true when complete:
<template>
<div>
<MyLoading v-if="$fetchState.pending" />
<MyContent v-else :posts="posts" />
</div>
</template>
<script>
export default {
data() {
return {
posts: []
}
},
async fetch() {
const { data } = await this.$axios.get(...)
this.posts = data
}
}
</script>
Customizing loading progress bar
Nuxt provides a default loading progress bar that appears at the top of the app while a page is loading. You could customize the progress bar's appearance:
// nuxt.config.js
export default {
loading: {
color: 'blue',
height: '5px'
}
}
Or you could specify your own custom loading component instead:
// nuxt.config.js
export default {
loading: '~/components/MyLoading.vue'
}
demo

NuxtJS - I want to know how to implement Loading only for specific pages

I want to display the Nuxt Loading component only on the top page, but it will be displayed on all pages. Can't I display it only when I access a specific page?
Also, the page will be output momentarily before the loading screen starts. Do you know what caused it?
nuxt.config.js
export default {
..
loading: '#/components/Organisms/PageLoading.vue',
..
}
layouts/default.vue
<template>
<div>
<org-page-loading />
</div>
</template>
<script>
import PageLoading from "../components/Organisms/PageLoading";
export default {
name: "Default",
components: {
"org-page-loading": PageLoading,
},
mounted() {
this.$nextTick(() => {
this.$nuxt.$loading.start()
setTimeout(function () {
this.$nuxt.$loading.finish()
}, 2400)
})
},
}
</script>
You can do something like this:
// your-component.vue
<template>
<h1>My Custom page</h1>
</template>
<script>
export default {
loading: false
}
</script>
So firstly the $nextTick probably makes your page show before loading.
Secondly I have never used this kind of loading component, but if you make your own loading component, you can pass a prop to it dynamically, which tells it on which page you want it to be and if the url is not matching, or the component name is not matching it will not show
<your-custom-loading :page="routerLinkYouWantItToBeShownOn" />
EDIT:
I found a better solution:
Heres the spinner/loading component
<template>
<div class="spinner"></div>
</template>
<style lang="css">
.spinner {
position: absolute;
height: 60px;
width: 60px;
border: 3px solid transparent;
border-top-color: var(--v-success-base);
top: 50%;
left: 50%;
margin: -30px;
border-radius: 50%;
animation: spin 2s linear infinite;
z-index: 100;
&:before,
&:after {
content: '';
position: absolute;
border: 3px solid transparent;
border-radius: 50%;
}
&:before {
border-top-color: var(--v-error-base);
top: -12px;
left: -12px;
right: -12px;
bottom: -12px;
animation: spin 3s linear infinite;
}
&:after {
border-top-color: var(--v-accent-lighten1);
top: 6px;
left: 6px;
right: 6px;
bottom: 6px;
animation: spin 4s linear infinite;
}
}
#keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
And here is how u call it:
<template>
<div>
<loading> v-if="isLoading" />
<div v-else>Your other content</div>
</div>
</template>
<script>
import Loading from '#/components/Loading';
export default {
components: {
Loading
},
computed: {
isLoading(){
return "your condition for loading"
}
},
</script>
And you can use a data variable for isloading set to true by default and when u visit the page it will be loading by default
dont even need props

How to make transition work with "visibility" but not "display"?

The transition element of vue only works with display:none but not visibility:hidden, is there any way to make it work with visibility? I want to get the clientWidth of the element before it shows up, with display:none I can't get that value.
By the way I'm using vue3.
Here is the reproduction demo:
https://codesandbox.io/s/competent-hermann-b1s5q
I'm going to assume, for the sake of argument, that you genuinely do need to use visibility for hiding and that other potential solutions (such as opacity) won't work in your real use case, possibly because they don't prevent user interactions with the element.
However, the assertion in the question is slightly misleading. It isn't really a difference between display and visibility. The real difference here is that the display case is using v-show, which includes special handling for transitions.
The current source code for v-show can be seen here:
https://github.com/vuejs/vue-next/blob/d7beea015bdb208d89a2352a5d43cc1913f87337/packages/runtime-dom/src/directives/vShow.ts
A similar approach can be used to construct a directive that uses visibility. Below is an example. It is based on the code for v-show but I've cut it back to just the code required for this particular use case:
const visible = {
updated(el, { value, oldValue }, { transition }) {
if (!value === !oldValue) {
return
}
if (value) {
transition.beforeEnter(el)
el.style.visibility = ''
transition.enter(el)
} else {
transition.leave(el, () => {
el.style.visibility = 'hidden'
})
}
}
}
Vue.createApp({
data() {
return {
show: true
};
},
methods: {
toggle() {
this.show = !this.show;
}
},
directives: {
visible
}
}).mount('#app')
#app {
text-align: center;
}
.tooltip-enter-active {
transition: transform 0.4s ease-out, opacity 0.3s ease-out;
}
.tooltip-leave-active {
transition: transform 0.35s ease-in, opacity 0.28s ease-out;
}
.tooltip-enter-from {
transition: none;
}
.tooltip-enter-from,
.tooltip-leave-to {
transform: translateY(-30px) scale(0.96);
opacity: 0;
}
<script src="https://unpkg.com/vue#3.0.2/dist/vue.global.prod.js"></script>
<div id="app">
<transition name="tooltip">
<div v-visible="show">
Using visibility
</div>
</transition>
<button #click="toggle">toggle message</button>
</div>
I did also have to make a small CSS change to give the enter transition a kick:
.tooltip-enter-from {
transition: none;
}
You'd probably be better off without <transition> in this case:
const app = Vue.createApp({
data() {
return {
show: true,
};
},
methods: {
toggle() {
const tooltip = this.$refs.tooltip;
this.show = !this.show;
tooltip.classList.toggle("tooltip-show");
},
},
mounted() {
console.log('Tooltip-width: ', this.$refs.tooltip.clientWidth);
},
});
app.mount('#app')
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.tooltip {
opacity: 0;
transform: translateY(-30px) scale(0.96);
transition: transform 0.35s, opacity 0.25s;
}
.tooltip-show {
opacity: 1;
transform: translateY(0) scale(1);
}
<script src="https://unpkg.com/vue#3.0.2/dist/vue.global.js"></script>
<div id="app">
<div class="tooltip" ref="tooltip">This will work!</div>
<button #click="toggle">toggle tooltip</button>
</div>

How to create the perfect autosize textarea?

I do auto expanding textarea.
The principle is this: I create a hidden div in which I place the input text, then in the updated () method I define the height of the div and apply the value to the textarea.
But there is one problem - there is text twitching, because First, the text crawls up, and then when the field is expanded, it returns to its place. As if the updated () method works late. By the same principle, I made a text field in the ReactJS there was no such effect.
What can do with it?
How it works: https://jsbin.com/zakavehewa/1/edit?html,css,js,console,output
<template>
<div class="textarea_wrap">
<textarea class="text_input textarea" v-model="value" ref="textarea"></textarea>
<div v-if="autoRow" class="text_input textarea shadow" ref="shadow">{{ value }}!</div>
</div>
</template>
<script>
export default {
props: {
autoRow: {
type: Boolean,
default: false
},
default: String
},
data () {
return {
value: this.default,
}
},
mounted() {
this.updateHeight()
},
updated() {
this.updateHeight()
},
methods: {
updateHeight() {
if (this.autoRow && this.$refs.shadow) {
this.$refs.textarea.style.height = this.$refs.shadow.clientHeight + 5 + 'px'
}
}
}
}
</script>
<style lang="scss" scoped>
.textarea_wrap {
position: relative;
}
.textarea {
line-height: 1.5;
min-height: 31px;
width: 100%;
font-family: inherit;
}
.shadow {
position: absolute;
left: -9999px;
pointer-events: none;
white-space: pre-wrap;
word-wrap: break-word;
resize: none;
}
</style>
Checkout https://github.com/wrabit/vue-textarea-autogrow-directive, it caters for rendering in hidden divs plus copying and pasting text.