How do I create a splash screen in VueJS? - javascript

I am trying to create a splash screen (loading-screen) in Vue JS that after a few seconds fades away, revealing my defaut view. I have tried several approaches but just can't get any to work. The closest is this example on CodePen But ideally the component wouldn't be inside main.js and instead inside its own component. Despite that the below code won't work.
My main.js is as below:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
// FILTERS
Vue.filter('snippet', function(value) {
return value.slice(0,100);
});
Vue.component('loading-screen', {
template: '<div id="loading">Loading...</div>'
})
new Vue({
router,
store,
render: h => h(App),
data: {
isLoading: true
},
mounted () {
setTimeout(() => {
this.isLoading = false
}, 3000)
}
}).$mount("#app");
and my App.vue is as follows
<template>
<div id="app">
<loading-screen v-if="isLoading"></loading-screen>
<Top/>
<router-view/>
<PrimaryAppNav/>
</div>
</template>
<script>
import Top from './components/Top.vue'
import PrimaryAppNav from './components/PrimaryAppNav.vue'
export default {
name: 'app',
components: {
Top,
PrimaryAppNav
}
}
</script>

A LoadingScreen.vue component could look like this:
<template>
<div :class="{ loader: true, fadeout: !isLoading }">
Loading ...
</div>
</template>
<script>
export default {
name: "LoadingScreen",
props: ["isLoading"]
};
</script>
<style>
.loader {
background-color: #63ab97;
bottom: 0;
color: white;
display: block;
font-size: 32px;
left: 0;
overflow: hidden;
padding-top: 10vh;
position: fixed;
right: 0;
text-align: center;
top: 0;
}
.fadeout {
animation: fadeout 2s forwards;
}
#keyframes fadeout {
to {
opacity: 0;
visibility: hidden;
}
}
</style>
Be aware that the loader needs to be aware if loading is done to be able to fade out. You need to check that in your App as well, so it's not showing while it still needs to prepare data. Otherwise, information from the app part in the background could be leaking (for example scrollbars might be visible on the LoadingScreen). So App.vue could have a template like this:
<template>
<div id="app">
<LoadingScreen :isLoading="isLoading" />
<div v-if="!isLoading">
...your main content here...
</div>
</div>
</template>
If you want to have the LoadingScreen divs to disappear completely, you need to manage the state of the fadeout animation in the App.vue itself, making it a bit more complicated (I'd probably use two props for LoadingScreen then: isLoading and fadeout, where fadeout is a callback that gets called in LoadingScreen as soon as the fadeout animation is complete).
I have prepared a codesandbox for you with the state management inside the LoadingScreen.

This is a working App.vue with a splash screen:
<template>
<div id="app">
<v-app :light="!nav.dark" :dark="nav.dark">
<transition name="slide-fade" mode="out-in">
<router-view></router-view>
</transition>
</v-app>
<div v-if="loading" style="position:absolute; width: 100%; height:100%; top:0; left:0; z-index:10000; background-color:white">
<div style="margin-left: auto; margin-right: auto">
Loading...
</div>
</div>
</div>
</template>
<script>
export default {
name: "app",
data: () => ({
loading: true
}),
mounted() {
setTimeout(() => {
this.loading = false
}, 3000)
}
}
</script>
Note that there is a z-index trick and mounted is in App component.
You can of course create a component just for loading, so it will become:
App.vue
<template>
<div id="app">
<v-app :light="!nav.dark" :dark="nav.dark">
<transition name="slide-fade" mode="out-in">
<router-view></router-view>
</transition>
</v-app>
<loader v-if="loading"/>
</div>
</template>
<script>
import Loader from "./Loader"
export default {
name: "app",
data: () => ({
loading: true
}),
mounted() {
setTimeout(() => {
this.loading = false
}, 3000)
}
}
</script>
Loader.vue
<template>
<div style="position:absolute; width: 100%; height:100%; top:0; left:0; z-index:10000; background-color:white">
<div style="margin-left: auto; margin-right: auto">
Loading...
</div>
</div>
</template>
<script>
export default {
name: "loader"
}
</script>
After that, I strongly suggest that you use dynamic components for your router components, Top and PrimaryAppNav. Like that they will load during your splash screen. You will find how to do that very easily in my answer in this thread (only section 2 is relevant for you): here

At beginning show fullscreen splash (with class binding listens for loadedApp).
When vuejs mounted, (or your any other process completed then, change data loadedApp = true.
Then fadeoutHide style will run and hides your splash
<div class="fullscreen-splash" :class="{fadeoutHide:loadedApp}">
// splash logo etc
</div>
data() {
return {
loadedApp: false
}
},
mounted() {
this.loadedApp = true
}
.fadeoutHide {
animation: fadeoutHide .5s forwards;
}
#keyframes fadeoutHide {
to {
opacity: 0;
visibility: hidden;
}
}

Related

Vue - Transitioning the route doesn't work

I'm using vue 3 with vue router, and I used the same code from the docs for transitioning the route, and it worked at first, but after some changes in the code in other files, it decided not to work ;-;
Here is my app.vue
<template>
<Nav />
<router-view v-slot="{ Component }">
<transition name="Fade">
<div>
<component :is="Component" />
</div>
</transition>
</router-view>
</template>
<script>
import Nav from "#/components/Nav.vue";
export default {
name: "HomeView",
components: {
Nav,
},
};
</script>
<style scoped>
.Fade-enter-from,
.Fade-leave-to {
opacity: 0;
transform: translateX(120px);
}
.Fade-enter-active,
.Fade-leave-active {
transition: all 0.5s ease-in;
}
</style>
and here's the Main Route
<template>
<div>
<WText />
<Softwares />
</div>
</template>
<script>
import WText from "#/components/WText.vue";
import Softwares from "#/components/Softwares.vue";
export default {
components: {
WText,
Softwares,
},
};
</script>
The other route (contact) is just an empty vue file, nothing special.
transition tag works when inner content re-renders (which could mean Component variable change, setting v-if="true" to v-if="false" and viceversa.), wrapping <component> tag with div will keep a constant dom element existing. Getting rid of wrapper div (or force rerender when Component variable changes which is not a good way to do it.)

In Vuejs3 how to make the sticky header component change background colour when intersecting with other components?

I am just wondering, how to structure my code to make my sticky <Header /> component change its background-color when intersecting with other components (in this case with <Hero /> and <Footer />.
To make things more complicated, I use vue-router.
Link to my CodeSandbox
The structure is as follows:
App.vue
<template>
<ul class="nav">
<li>
<router-link to="/">Home</router-link>
</li>
<li>
<router-link to="/about">About</router-link>
</li>
</ul>
<Header />
<router-view />
</template>
views/Home.vue
<template>
<Child1 />
<Child2 />
<Hero />
<Child3 />
<Footer />
</template>
<script>
import Hero from "#/components/Hero";
import Child1 from "#/components/Child1";
import Child2 from "#/components/Child2";
import Child3 from "#/components/Child3";
import Footer from "#/components/Footer";
export default {
components: {
Hero,
Child1,
Child2,
Child3,
Footer,
},
};
</script>
When I follow some tutorials on yt, the authors use the IntersectionObserver, so I wanted to give it a try:
Header.vue
<template>
<section
class="header"
>
<div class="header__title"
:class="{
header__scroll: isContrastActive,
}"
>
I am an awesome header that is changing it's background colour <br />
when intersecting with other components
</div>
</section>
</template>
<script>
export default {
data() {
return {
isContrastActive: false,
observer: null
};
},
methods: {
activateObserver() {
this.observer = new IntersectionObserver(
([entry]) => {
console.log("isIntersecting: ", entry.isIntersecting);
if (!entry.isIntersecting) {
this.isContrastActive = true;
} else {
this.isContrastActive = false;
}
},
{ rootMargin: "-5% 0px 0px 0px" }
);
document
.querySelectorAll(".observer")
.forEach((el) => this.observer.observe(el));
},
},
mounted() {
this.activateObserver();
},
};
</script>
<style scoped>
.header {
position: sticky;
top: 0;
width: 100vw;
height: auto;
z-index: 10;
}
.header__title {
background-color: yellow;
color: black;
padding: 8px 4px;
text-align: center;
}
.header__scroll {
background-color: orange;
}
</style>
Also added the class="observer" to the <Hero /> and <Footer /> components.
But it doesn't seem to work.
I have read, that in vue the use of querySelector is considered to be an antipattern.
If this is the case then how I could structure my code to get what I want?
Thanks in advance
EDIT1:
I noticed, that if I change the text in the <Header /> component and save then everything seems to work just fine.
But on page refresh, it is still not working.
Think that the querySelectorAll could be the reason.
SOLUTION 1:
The most obvious solution is of course:
mounted() {
setTimeout(() => {
this.activateObserver()
}, 500)
},
but it ain't an elegant solution :/.
The code you're using makes the assumption all observed elements are already in DOM.
You need to make the observable available to all your pages (use the preferred state management solution).
Your activateObserver should become:
createObserver() {
someStore().observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) {
this.isContrastActive = true;
} else {
this.isContrastActive = false;
}
},
{ rootMargin: "-5% 0px 0px 0px" }
);
}
Then, in any page which has observed elements, use template refs to get all elements you want observed.
In onMounted you start observing them, in onBeforeUnmount you stop observing them.
const observed = ref([]);
onMounted(() => {
observed.value.forEach(someStore().observer.observe)
})
onBeforeUnmount(() => {
observed.value.forEach(someStore().observer.unobserve)
})
How you select your elements using template refs is irrelevant for this answer. The point is you need to start observing the DOM elements referenced after the component has been mounted and unobserve them before unmount.
Note: in theory, it is possible that any of your views is rendered before the observer has been added to the store. (In practice it's a rare case, so you probably don't need this).
However, if that's the case, you want to create a watcher for the observer in your pages and start observing only after you have the observer, which might be after the page has been mounted.
The principle is simple: you need both observer and observed for this to work. observer is available after it is added to store, observed are available after onMounted until onBeforeUnmount in any of the pages containing them.

How to change body style from vue.obserable store value

It is my first time to work with Vue or Web. I have been to working with C to write firmware.
Depends on sidebar state, I would like to change padding-right value of body in App.vue.
whenever button is pushed, isNavOpen value is changed
import Vue from "vue";
export const store = Vue.observable({
isNavOpen: false
});
export const getters = {
getNavOpen: () => store.isNavOpen
}
export const mutations = {
setIsNavOpen(yesno) {
store.isNavOpen = yesno;
},
toggleNav() {
store.isNavOpen = !store.isNavOpen;
}
};
so I would like to change padding-right value of body in App.vue
<template>
<div id="app">
<nav class="main-nav">
<div class="logo">my.company</div>
<Burger></Burger>
</nav>
<Sidebar>
<ul class="sidebar-panel-nav">
<li>
Home
</li>
<li>
About
</li>
<li>
Contact
</li>
</ul>
</Sidebar>
<router-view />
</div>
</template>
<script>
import Burger from "./components/Menu/Burger.vue";
import Sidebar from "./components/Menu/Sidebar.vue";
import { store, getters, mutations } from '#/store.js'
export default {
name: "app",
components: {
Burger,
Sidebar
},
data:{
return: {
isSidebarOpen: this.$getters.getNavOpen()
}
}
};
</script>
<style>
html {
height: 100%;
overflow: hidden;
}
body {
border: 0;
margin: 0;
padding-left: 350px;
font-family: "Lato";
height: 100%;
background: rgb(101, 31, 87);
background: linear-gradient(
45deg,
rgba(101, 31, 87, 1) 0%,
rgba(225, 113, 87, 1) 48%,
rgba(249, 248, 113, 1) 100%
);
}
</style>
I read pages. However it is so hard to combine these codes for me
change style
vue sidebar tutorial
or Is any way to implmement to change padding by sidebar states?
Some thing might be missing in your second block but i assume that you successfully can use true or false value as isSideBarOpen. All you need to do is to use style binding(you can also use class binding but you need to define specific class for padding right) Here is what you need:
<div id="app" :style="(isSideBarOpen) ? 'padding-right: 10px;' : ''">
/* code */
</div>

Why this vue transformation doesn't work as expected?

I'm trying to code simple slide panel, like here http://anton.shevchuk.name/wp-demo/jquery-tutorials/simple-slide-panel.html
but on Vue.js. But panel doesn't slide it's wait 2 seconds then close without animation.
https://codepen.io/TogusaRusso/pen/dqoMLr
If I remove v-if, I can animate it, changing height of div.
https://codepen.io/TogusaRusso/pen/mGJEEJ
How can I animate removing of div?
<template>
<v-flex xs12 sm6 offset-sm3>
<transition name="slide" :duration="2000">
<div
class="slide-media"
:style="{height: '400px'}"
v-if="isOpen"
>Hello!</div>
</transition>
<v-btn
flat color="orange"
#click="isOpen=!isOpen"
>
{{isOpen?"Close":"Open"}}
</v-btn>
</v-flex>
</template>
<script>
export default {
name: 'SlidePanel',
data() {
return {
isOpen: false,
}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.slide-media {
background-color: orangered;
overflow-y:hidden;
}
.slide-enter-active, .slide-leave-active {
transition: height 2s;
}
.slide-enter, .slide-leave-to {
height: 0px;
}
</style>
Check MDN: Css Specificity,
Inline styles added to an element (e.g., style="font-weight:bold")
always overwrite any styles in external stylesheets, and thus can be
thought of as having the highest specificity.
So removed the inline styles :style="{height: '400px'}", then add height:400px; inside .slide-media.
Below is one demo:
new Vue({
el: '#app',
data(){
return {
isOpen: false
}
},
methods: {
togglePanel: function () {
this.isOpen = !this.isOpen
}
}
})
.slide-media {
background-color: orangered;
overflow-y:hidden;
height:400px;
}
.slide-enter-active, .slide-leave-active {
transition: height 2s;
}
.slide-enter, .slide-leave-to {
height: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button #click="togglePanel()">Toggle Panel</button>
<transition name="slide" :duration="2000">
<div
class="slide-media"
v-if="isOpen"
>Hello!</div>
</transition>
</div>

Control modals using vue router

I am working in the project developed with Vue 2 with VueRouter and I am trying to work with my modals controlled by my VueRouter!
I've done the following code
Main vue component: My normal components will be loaded on the default router-view and all my modals will be loaded on the modal router-view
<div id="app">
<router-view v-if="!isLoading"></router-view>
<router-view v-if="!isLoading" name="modal"></router-view>
</div>
RoutedModals Mixing
As you can see on my beforeRouteEnter method I am checking if there is a previous "from" route (which means the user got the page navigating inside the app)... If it's I set that one as default component... if not (which means the user got directly from the URL) I set my dashboard as default and it will be opened behind my modal.
import Dashboard from 'modules/dashboard/components/Main.vue'
import { isNil } from 'lodash'
export default {
data() {
return {
canAccessDirect: true,
goBackTo: '/'
}
},
beforeRouteEnter(to, from, next) {
to.matched[0].components.default = isNil(from.matched[0]) ? Dashboard : from.matched[0].components.default
next(vm => {
if (!vm.canAccessDirect)
vm.$router.push({
name: 'dashboard.index'
})
vm.fetchRecords()
vm.goBackTo = from.path
window.jQuery(vm.$el).modal('show')
window.jQuery(vm.$el).on('hide.bs.modal', () => {
vm.$router.push(vm.goBackTo)
})
})
},
beforeRouteLeave(to, from, next) {
setTimeout(() => {
next()
}, 200)
},
methods: {
fetchRecords() {
// Do list request
}
}
}
An example of my router object: The first route will open a modal on the router-view modal and the second will open only on the default router-view
{
name: 'leads.quick-add',
path: '/leads/quick-add',
components: { modal: QuickAdd },
},
{
name: 'leads.index',
path: '/leads',
component: Main,
},
It works great! The problem comes when I access my modal URL (does not matter if it's directly or navigating) and the default component has a child component! The child component get away on that case!
There is attached some screenshots to help you out understand what happens...
Image 1
Image 2
At Image 1 we can 2 components where the number 1 is my default component on my VueRouter and the number 2 is his child!
Ar the Image 2, after clicking on the + Quotation button the modal is loaded and the component number 2 getaway!
Any ideas on how to do it keeping the others components?
Just to be clear I want to do it by routing and no calling my modal manually!
########################## Edit
I am trying to do something like that instead of check on beforeRouterEnter method:
{
name: 'leads.show.quotations.create',
path: '/leads/:id/quotations/create',
components: {
default: Show,
'default.tab': Quotations,
modal: Add
},
meta: {
requiresAuth: true
}
},
Where there is a sub-router-view but it does not work!
Thinking about possibilities I've added this issue on the github repo:
https://github.com/vuejs/vue-router/issues/1030
I did this in a project at work. It is quite simple actually, the hard part lies in mixing the jquery that you have there, and maybe the css to position a modal, since it's entry point is the router-view inside your component I recommend using the package portal-vue to move the modal content to the end of the body.
Here is a working exemple: https://codesandbox.io/s/vue-router-modal-j10t2
First create a Modal Component that receives it's child by props:
<template>
<portal to="modal">
<div class="modal-wrapper">
<div class="overlay" #click="$router.back()"></div>
<div class="modal">
<!-- you can use v-bind to pass down all props -->
<component :is="component" v-bind="$attrs"/>
</div>
</div>
</portal>
</template>
<script>
export default {
name: "Modal",
props: ["component"],
};
</script>
<style scoped>
.modal-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.modal {
position: absolute;
z-index: 1;
margin: auto;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 5em;
border: 1px solid #ccc;
border-radius: 1em;
box-shadow: 0 0 1em #00000033;
}
.overlay {
z-index: 1;
position: absolute;
width: 100vw;
height: 100vh;
background-color: #00000055;
}
</style>
Then you can use the props in routes to assign the content for a given route:
import Home from "./Home.vue";
import Modal from "./Modal.vue";
import Content from "./Content.vue";
const router = new VueRouter({
routes: [
{
path: "/",
component: Home,
children: [
{
path: "create",
component: Modal,
props: {
component: Content
}
}
]
}
]
});
Since the modal is a child route of '/', the Home component needs to render the router-view:
<template>
<div>
Hi i'am home
<router-view />
<router-link to="/create">open modal</router-link>
</div>
</template>
And the App.vue needs to render the portal target, so the modal goes to the end of your content (it makes positioning the modal a lot easier):
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<router-view />
<portal-target name="modal" />
</div>
</template>
<script>
export default {
name: "App",
};
</script>
And that's it

Categories

Resources