How rendering a slot using v-if based on element width dynamically - vue.js

I create a responsive component. The idea is when the window resizes happens is checked if the window width is lower than breakpoint 960px (based on vuetify breakpoint). So, trigger a slot based on your respective slot directive (mobile or desktop).
The problem is: even when the component is rendered, he has the initial state has mobile, because toolbarWidth is equal to 0.
I want this to be dynamic.
Code:
data() {
return {
toolbarWidth: 0
}
},
created() {
window.addEventListener('resize', this.handleResize)
},
destroyed() {
window.removeEventListener('resize', this.handleResize)
},
computed: {
isMobile() {
return this.toolbarWidth < this.$vuetify.breakpoint.thresholds.sm
},
},
methods: {
handleResize() {
if (this.$refs.toolbar) {
this.toolbarWidth = this.$refs.toolbar.clientWidth
}
}
},
Slots inside template:
<v-menu v-if="isMobile">
...
<slot name="actionsMobile" />
</v-menu>
<slot v-else name="actionsDesktop" />

my advice is to use CSS instead Javascript to manage design , you can use for example Bootstrap class like col-12 for large screen col-md-12 for medium screen and col-sm-12 for small design .
There are a lot of options like
or you can use media query :
// Extra small devices (portrait phones, less than 576px)
// No media query since this is the default in Bootstrap
// Small devices (landscape phones, 576px and up)
#media (min-width: 576px) { ... }
// Medium devices (tablets, 768px and up)
#media (min-width: 768px) { ... }
// Large devices (desktops, 992px and up)
#media (min-width: 992px) { ... }
// Extra large devices (large desktops, 1200px and up)
#media (min-width: 1200px) { ... }
You can use these helpful links also:
media-query
bootstrap grid options
I hope that can help you to resolve your issue .

Related

enter and leave classes of vue transition doesn't work

I created this codepen, which is a simple flip card and it works fine in codepen, but when I add this project in my vue project created with cli, everything works fine; upon clicking a card, it shows back of the card, but it doesn't apply that transition so user can visually see that it is rotating. It rotates very fast, sounds like transition is not effecting.
This is the template code
<div v-for="card in cards" #click="toggleCard(card)" :key="card.id">
<transition name="flip">
<div
v-bind:key="card.flipped"
v-html="card.flipped ? card.back : card.front"
></div>
</transition>
</div>
and the script code
export default {
name: "FlipCard",
data() {
return {
cards: [
// cards here
],
};
},
methods: {
toggleCard: function (card) {
const isFlipped = card.flipped;
this.cards.forEach((strategy) => {
strategy.flipped = false;
});
isFlipped === true ? (card.flipped = false) : (card.flipped = true);
},
},
};
and css code:
.flip-enter-active {
transition: all 2s ease;
}
.flip-leave-active {
display: none;
}
.flip-enter,
.flip-leave {
transform: rotateY(180deg) !important;
opacity: 0;
}
can anyone help why in vue cli project the transition is so fast or maybe not applying?
Thank you in advance
The codepen you provided uses Vue 2. Your question is tagged Vue 3, so I assume you are using Vue 3.
Vue 3 made changes to transition class names - https://v3-migration.vuejs.org/breaking-changes/transition.html#_2-x-syntax.
-enter and -leave are now -enter-from and -leave-from.

VueRouter: Is it possible to have a smooth scroll down animation from one route to another?

I have a simple VueJS application where I have multiple routes. For a pair of routes, I want to have a scroll down and scroll up animation while routes change.
For example, I have a search/dropdown page, where after the search result from the dropdown is selected, I want to take him to the details page but with a scroll down animation. So that the user feels he is still on the same page.
I have tried using the VuePageTransition library. That is indeed a great library but does not have this specific animation that I need.
Update:
I tried the following code. It gives a scroll-like animation but the leaving page is shown going down but the coming page is not shown during the animation.
In the template in App.vue
<template>
<div id="app">
<transition name="slide" mode="out-in">
<router-view></router-view>
</transition>
</div>
</template>
In the style tag,
.slide-enter {
}
.slide-enter-active {
animation: slide-in-coming 2s ease-out forwards;
}
.slide-leave {
}
.slide-leave-active {
animation: slide-in 2s ease-out forwards;
}
#keyframes slide-in {
from {
transform: translateY(0);
}
to {
transform: translateY(800px);
}
}
#keyframes slide-in-coming {
from {
transform: translateY(-800px);
}
to {
transform: translateY(0);
}
}

Vue router in-out page transition: transition in a new route while old route remains visible

To illustrate what I'm trying to achieve but also discuss and learn about each mechanism separately, I split the issue into two independent challenges:
1. Keep previous route visible until new route has transitioned in
Whether the transition is sliding, what I'm trying here, or just fading; mode in-out doesn't work as I would expect it, namely that the existing route stays visible until the next route has finished its transition (e.g. overlaid itself over the previous one), exactly as illustrated here in the last example of this section https://v2.vuejs.org/v2/guide/transitions.html#Transition-Modes, showing two buttons with in-out mode. Instead no transition is happening but it just flips the routes statically at half of the given transition time.
Is there any caveat with routes and an obvious reason why this wouldn't work the same way, e.g. that a single router-view can only hold one at the time and therefore in-out is not possible?
EDIT 1:
I figured out that in-out would actually only work with position:absolute on both elements, otherwise they will not overlay. Any idea how I could elegantly include such a behavior, potentially setting that absolute position during router-transition only?
Current hack that has the visual slide-up modal effect (mode: in-out) I'm looking for: adding style="position:absolute; z-index:2100" to the dialog route. Then I would need to change the underlying transition once it's shown in order to have the reverse hide effect (mode: out-in).
Also see EDIT 2 below.
2. Creating a modal-like page (route) which opens above another existing page when navigated to
I tried to hack that behavior by adding a second router-view in App.vue
<router-view />
<router-view name="dialog" />
The particular component is added to my routes like this
{
path: 'records/new',
components: {
dialog: () => import('layouts/NewRecord.vue')
},
children: [
{
name: 'new-record',
path: '',
component: () =>
import('src/pages/NewRecord.vue')
}
]
}
I'm not sure whether this approach even makes sense but I couldn't make it work properly. The aim would be to just overlay another router-view name="dialog whenever a "dialog"-path is pushed, so while it can be animated (slide-up) the other router-view stays visible below. In the end I guess I'm facing the same issue here: once the route changes, the initial router-view discards its component because the path does not match the current location anymore.
Either way, there are people out there with more experience and expertise so I hope I could illustrate what I'm trying to achieve and I'm just curious and thankful to read your inputs.
EDIT 2
I could make it work the way I wanted with simply one , wrapped in a custom page-transition component. It is quite a hack though AND I needed to add position: absolute to may page-layouts, to all of them actually (both the "leaving" and the "entering" component need position: absolute) when showing the dialog component. I'm sure there's a better way but I haven't found it so far.
Custom page-transition component:
<template>
<transition :name="name" :mode="mode">
<slot/>
</transition>
</template>
<script lang="ts">
import { Component, Watch } from 'vue-property-decorator'
import Vue from 'vue'
import { Route } from 'vue-router'
#Component({
components: {}
})
export default class PageTransition extends Vue {
NAME_FADE = 'fade'
NAME_SLIDE_UP = 'slide-up'
NAME_SLIDE_DOWN = 'slide-down'
MODE_OUT_IN = ''
MODE_IN_OUT = 'in-out'
name = this.NAME_FADE
mode = this.MODE_OUT_IN
#Watch('$route', { immediate: true, deep: true })
onRouteChanged(newVal: Route, oldVal: Route) {
if (newVal.meta.transition === 'dialog') {
this.name = this.NAME_SLIDE_UP
this.mode = this.MODE_IN_OUT
} else if (oldVal && oldVal.meta.transition === 'dialog') {
this.name = this.NAME_SLIDE_DOWN
// shift next page in immediately below dialog
this.mode = this.MODE_IN_OUT
} else {
// default
this.name = this.NAME_FADE
this.mode = this.MODE_OUT_IN
}
}
}
</script>
<style lang="scss" scoped>
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: all 0.1s ease;
}
// start of enter element
.slide-up-enter {
transform: translateY(60%);
opacity: 0;
}
.slide-up-enter-active {
transition: all 0.3s ease-out;
z-index: 2100;
}
// start of leave element
.slide-up-leave, .slide-up-leave-active {
opacity: 0;
}
// start of leave element
.slide-down-leave {
z-index: 2100;
}
.slide-down-leave-to {
transform: translateY(60%);
opacity: 0;
z-index: 2100;
}
.slide-down-leave-active {
transition: all 0.3s ease-in;
}
// start of enter element
.slide-down-enter {
opacity: 0;
}
.slide-down-enter-active {
/* show immediately behind existing page (lower z-index) */
transition: all 0s;
}
</style>
I have a similar task. I was able to complete it using fixed containers and z-index shuffle. I met a number of issues related to scroll and vertical alignment, and, in my case, solving it using absolute position during router-transition only was not possible.
Here's the demo: https://kasheftin.github.io/vue-router-in-out-slide-scroll.
Also, I had to use localStorage to keep & restore page scroll position.
In my case page content has to be vertically aligned. That's why I could not use one global scrollable container (e.g. <body>). In-out mode transition works rather simple - it just appends the content, adds some classes and then removes the first child. That means in the middle there're two page containers side by side, and if one of them is tall (and forces the body to have scroll), then the other one appears in the middle of the body and has wrong vertical alignment.
So I just wrapped every page with fixed scrollable container. Assume we have a List and an Item pages, and the last should slide from the right and overlay the list. Then, the right-to-left animation is very simple:
.slide-right-enter-active {
transition: transform 1s ease;
.slide-right-enter {
transform: translateX(100%);
}
Left-to-right animation (overlay disappearing) has the wrong z-index. During the animation we have the following in the DOM:
<transition>
<Item />
<List />
</transition>
By default List will be shown over the Item, but it has to be below. So there're the rules:
.slideable-page {
position: fixed;
overflow: auto;
z-index: 2;
}
.slide-left-enter {
z-index: 1;
}
.slide-left-enter-active {
z-index: 1;
}
.slide-left-leave-active {
transition: transform 1s ease;
z-index: 3;
}
.slide-left-leave-to {
transform: translateX(100%);
}
For question 1: Have you added the CSS with it? The transition by itself only handles timing, you need to add the CSS for the transition to work (example: https://v2.vuejs.org/v2/guide/transitions.html#Transitioning-Single-Elements-Components).
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
For question 2:
I don't know if I understood correctly your situation, but if I did, here is what I would do, using nested routes.
layouts/NewRecord.vue
<template>
<router-view name="dialog"></dialog>
</template>
Routes
const routes = {
path: 'records/new',
component: () => import('layouts/NewRecord.vue'),
children: [
{
path: 'dialog',
components: {
dialog: () => import('src/pages/NewRecord.vue'),
},
},
],
}

How to do width transition in Tailwind CSS?

When I try to do a transition using the default "w-#" options in Tailwind, my transitions don't apply. When I hard code in my own classes for width, it works fine. Is there something weird with Tailwinds CSS and how it handles width that would cause this?
Here's the HTML text. The main part here is the dynamic class "sidebarWidth" which switches when the button is clicked. The transition-all, slowest and ease are all things I extended in Tailwind.
<nav class="text-white absolute md:relative flex-col min-h-full bg-black mt-24 md:mt-12 transition-all transition-slowest ease" :class="sidebarWidth">
Here's the JS code in the computed properties of the Vue component
sidebarWidth: function() {
if (this.$store.getters.isSidebarCollapsed) {
return "w-14 invisible md:visible";
} else {
return "w-64";
}
}
If I swap out w-14 and w-64 for the following classes, it works great.
<style scoped>
.width1 {
width: 100px;
}
.width2 {
width: 400px;
}
</style>
I basically want my sidebar nav to slide in when I click a button. In mobile, the sidebar nav is hidden and I want it to slide out. In the desktop, it should be a small nav and then slide out to a full screen nav. It works, but the slide transition doesn't work. Also, the margin change between mobile and desktop does animate properly.
You need to perform a few steps to activate the behaviour you are looking for.
The first one is about extending you Tailwind theme via tailwind.config.js. You need to add the transitionProperty for width.
module.exports = {
...
theme: {
...
extend: {
...
transitionProperty: {
'width': 'width'
},
},
},
}
The changes above create the transition-width class. Simply apply this one to your nav tag. In your specific case you can overwrite the transition-all class.
<nav class="text-white absolute md:relative flex-col min-h-full bg-black mt-24 md:mt-12 transition-width transition-slowest ease" :class="sidebarWidth">
The last step is quite easy: ensure that Tailwind is recreating the CSS. Afterwards you should see a smooth width transition in your project.
Background to the Problem
By default, the width and height transitions are not activated in Tailwind CSS. Here is the CSS that will be applied when using transition class.
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
Like you can see width and height are missing in transition-property.
You can find more information about it in the Tailwind documentation.
You can make your own transition property like in tailwind.config.js :
Multiple properties :
module.exports = {
theme: {
extend: {
transitionProperty: {
multiple: "width , height , backgroundColor , border-radius"
}
}
}
One property :
module.exports = {
theme: {
extend: {
transitionProperty: {
width: "width"
}
}
}

Resize button according to screen size

For a button, by default Bootstrap 4 allow you to set default button "size" between : xs, sm, md, lg, xl.
So, in my code, small screen first, i use sm size for screen <576px :
<button type="button" class="btn btn-success btn-sm"></button>
But for xl screen ≥1200px, need i to change size attribute or something else with Bootstrap to adjust button size ?
I don't really understand Bootstrap responsive behavior for button and 'size' attribute between small and large screen.
Thanks.
I don't think there's anything built out of the box for responsive buttons in bootstrap, you'd probably be better off extending the existing bootstrap button sizes in sass/media queries ie
.responsive-button {
#media (min-width: 576px) { #extend .btn-sm }
#media (min-width: 768px) { #extend .btn-md }
}
I haven't tested this so may need to research a bit further but hopefully this gets you on track :)
According to the Vue.js documentation, i had finally computed my CSS class dynamically according to window.onresizeevent call in mounted () function.
Example :
Here is my Bootstrap button :
<b-button :size="nsize" variant="outline-success" class="my-2 my-sm-0">
<font-awesome-icon icon="search"/>
</b-button>
Here is my function in App.vue file:
<script>
export default {
name: 'topnavbar',
data () {
return {
nsize: "sm",
mediaWidth: Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
}
},
mounted () {
if (window.addEventListener) {
window.addEventListener("resize", this.updateSize, false);
} else if (window.attachEvent) {
window.attachEvent("onresize", this.updateSize);
}
},
methods : {
updateSize: function (){
let sizeEl = "md";
let width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
if(width <= 576){
sizeEl = "sm";
}
this.nsize = sizeEl;
}
}
}
</script>
Sources:
Get the browser viewport dimensions with JavaScript
https://fr.vuejs.org/v2/guide/class-and-style.html