trying to make a slider by Vue.js
Have a structure like that
<slider>
<slide> 1 </slide>
<slide> 2 </slide>
<slide> 3 </slide>
</slider>
In parent component i've catch the slides through $slots.
В родительском компоненте (slider) получаю слайды через $slots.
There is no problems with a standart translate animation. But if i want add a fade animation to slides, i have to:
.slider {
position: relative;
}
.slide {
position: absolute;
top: 0;
left: 0;
}
So, i've to set a height of parent element equal to highest slide.
Trying to do it in mounted hook:
mounted() {
this.slides.forEach((item) => {
console.log(item.elm.clientHeight);
})
}
But it say like:
slide1 height: 1559
slide2 height: 1915
slide3 height: 2371
But real height is:
slide1 height: 431
slide2 height: 315
slide3 height: 347
Mounted hook doesn't guarantee that children will be mounted. I have to set a height in
mounted() {
this.$nextTick(() => {
// code
})
}
MDN is a good resource.
Try this.
this.slides.forEach((item) => {
console.log(item.elm.offsetHeight);
})
Related
The slider I am building have the active slider bigger than the others. I managed to make it work without the animation with flkty.reposition(). However, I am trying now to add the animation where the next slide grows in and the active decrease out. For The animation I am using GSAP.
The issue I am facing is to overwrite the left property with gsap so that it continuous animate. As of now, the left property (controlled by Flickity) does not take into account the final size (controlled by GSAP) of the selected slide.
https://codepen.io/stefanomonteiro/pen/VwzwjLw?editors=0010
As the left property of each slide is controlled by Flickity, we could use margin-left with a minus value as an alternative property to pull the selected slide to the left. I know margin is not a good property to animate but it works in this case without digging too deep into the Flickity core.
Here is the GSAP code:
gsap.to(slides, {
duration: 1,
width: "220px",
height: "336px"
});
gsap.to(selectedSlide, {
duration: 1,
marginLeft: "-248px", // the empty space calculated by newWidth - oldWidth
width: "468px",
height: "630px",
onComplete: () => {
// once all animations have been settled, we reset the margin
gsap.set(selectedSlide, { marginLeft: "" });
// and tell Flickity to update
flkty.resize();
flkty.reposition();
}
});
And the snippets
const animate = () => {
const flkty = Flickity.data(".carousel");
const selectedSlide = flkty.selectedElement;
const slides = flkty.getCellElements();
// remove the selected slides
slides.splice(flkty.selectedIndex, 1);
gsap.to(slides, {
duration: 1,
width: "220px",
height: "336px"
});
gsap.to(selectedSlide, {
duration: 1,
marginLeft: "-248px", // the empty space calculated by newWidth - oldWidth
width: "468px",
height: "630px",
onComplete: () => {
// once all animations have been settled, we reset the margin
gsap.set(selectedSlide, {
marginLeft: ""
});
// and tell Flickity to update
flkty.resize();
flkty.reposition();
}
});
};
new Flickity(".carousel", {
cellAlign: "right",
wrapAround: true,
percentPosition: false,
on: {
ready: () => animate()
}
});
const nextButton = document.querySelector(".flickity-button.next");
nextButton.addEventListener("click", () => animate());
/* external css: flickity.css */
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
.carousel {
background: #EEE;
}
.carousel-cell {
width: 220px;
height: 336px;
margin-right: 20px;
background: #8C8;
border-radius: 5px;
counter-increment: carousel-cell;
}
.carousel-cell.is-selected {
width: 468px;
height: 630px;
z-index: 1;
}
/* cell number */
.carousel-cell:before {
display: block;
text-align: center;
content: counter(carousel-cell);
line-height: 200px;
font-size: 80px;
color: white;
}
<link href="https://npmcdn.com/flickity#2/dist/flickity.css" rel="stylesheet" />
<script src="https://unpkg.co/gsap#3/dist/gsap.min.js"></script>
<script src="https://npmcdn.com/flickity#2/dist/flickity.pkgd.js"></script>
<h1>Flickity - wrapAround</h1>
<!-- Flickity HTML init -->
<div class="carousel">
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
</div>
And the Codepen
You can also notice that we have to wait until the animation is finished until we perform the next click, otherwise, it would mess up the whole process. This is predictable. Hence, I personally will try not to manipulate this Flickity slider for this kind of animation. Just want to give you a solution, anyway.
I've tried the following, but nothing is displayed, except of arrows:
<template lang="pug">
b-carousel(
id='categoryRoulette'
controls
no-animation
:interval='0'
)
b-carousel-slide
b-card(
img-src='https://licota.ru/system/product_category_images/attachments/55b9/ea79/7372/7609/da00/0000/home/a87c8365-bf1f-11df-a726-0015175303fd.png?1438247544'
img-alt='Image1'
img-top=''
tag='article'
)
b-card-text.centerText
h5
a(href="#") Text1
b-carousel-slide
b-card(
img-src='https://licota.ru/system/product_category_images/attachments/55b6/8675/7372/7679/ac00/0000/home/a87c8368-bf1f-11df-a726-0015175303fd.png?1438025333'
img-alt='Image2'
img-top=''
tag='article'
)
b-card-text.centerText
h5
a(href="#") Text2
b-carousel-slide
b-card(
img-src='https://licota.ru/system/product_category_images/attachments/55b6/7bf6/7372/762b/cb00/0000/home/49143ab4-7b92-11e4-80f3-002590d99cf6.png?1438022646'
img-alt='Image3'
img-top=''
tag='article'
)
b-card-text.centerText
h5
a(href="#") Text3
b-carousel-slide
b-card(
img-src='https://licota.ru/system/product_category_images/attachments/55b6/7c0a/7372/762b/cb00/002a/home/a87c8369-bf1f-11df-a726-0015175303fd.jpeg?1438022665'
img-alt='Image4'
img-top=''
tag='article'
)
b-card-text.centerText
h5
a(href="#") Text4
</template>
<script lang="ts">
import testData from '../data/testData.json';
import { RouletteData } from '../types/roulette'
import { Category } from '../types'
import Vue from 'vue'
export default Vue.extend({
data: (): RouletteData => ({
slide: 0,
sliding: false,
catalog: [],
}),
mounted(): void {
this.catalog = testData.catalog
},
methods: {
onSlideStart() : void {
this.sliding = true
},
onSlideEnd() : void {
this.sliding = false
},
},
})
</script>
<style lang="sass">
#categoryRoulette
margin-bottom: 40px
margin-top: 40px
.carousel-caption
color: black
.carousel-control-prev-icon
margin-left: -200px
.carousel-control-next-icon
margin-right: -200px
.carousel-control-next-icon::after
color: rgb(0, 70, 140)
content: '>'
font-size: 55px
.carousel-control-prev-icon::after
color: rgb(0, 70, 140)
content: '<'
font-size: 55px
.centerText
align-items: center
display: flex
justify-content: center
</style>
Since your carousel slide has no direct image (you put the image in the <b-card>), the slide will collapse unless you do something. The first option is to set the img-blank attribute. The first example in the carousel docs does that and has a comment:
Slide with blank fluid image to maintain slide aspect ratio
Use it like this:
<b-carousel-slide img-blank>
Or if you want to modify the CSS directly, you can set a height in the .carousel-item class:
.carousel-item {
height: 300px;
}
Also, you should use a v-for for your slides instead of hard-coding them:
<b-carousel
id='categoryRoulette'
controls
no-animation
:interval='0'
>
<b-carousel-slide v-for="(slide, index) in slides" img-blank :key="index">
<b-card
:img-src="slide.image"
img-alt="Image1"
img-top=""
tag="article"
>
...
</b-card>
<b-carousel-slide>
</b-carousel>
slides: [
{ image: 'https://licota.ru/system/product_category_images/attachments/55b9/ea79/7372/7609/da00/0000/home/a87c8365-bf1f-11df-a726-0015175303fd.png?1438247544'},
{ image: 'https://licota.ru/system/product_category_images/attachments/55b6/8675/7372/7679/ac00/0000/home/a87c8368-bf1f-11df-a726-0015175303fd.png?1438025333'},
{ image: 'https://licota.ru/system/product_category_images/attachments/55b6/7bf6/7372/762b/cb00/0000/home/49143ab4-7b92-11e4-80f3-002590d99cf6.png?1438022646'},
{ image: 'https://licota.ru/system/product_category_images/attachments/55b6/7c0a/7372/762b/cb00/002a/home/a87c8369-bf1f-11df-a726-0015175303fd.jpeg?1438022665'},
]
I make a spinner component in my project and I pass some props to load the spinner and it's like this
Vue.component("Spinner", require("./components/Loading/Loading.vue").default, {
props: ["loading"]
});
.spinner {
width: 100%;
height: 100%;
background-color: white;
position: fixed;
transition: 0.5s;
z-index: 999;
}
.ring {
position: absolute;
top: 50%;
left: 50%;
margin: 0 auto;
height: 100%;
z-index: 1000;
}
<template>
<div class="spinner">
<div class="ring">
<half-circle-spinner :size="60" color="#1ABC9C"/>
</div>
</div>
</template>
and I call it in the other components like this:
<template>
<Spinner key="list-key" :loading="true"/>
</template>
The thing I need is to make this.spin = false when all the DOM elements are loaded on my page. please let me know your ideas. :)
We'll initialize a isLoading variable to true when the component is created and then set it to false in the mounted hook - by using nextTick in the mounted hook it will execute after all dom children have been loaded: https://v2.vuejs.org/v2/api/#mounted
You can try using v-if & v-else to show/hide the content/spinner based on a data attribute (such as isLoading) you can change once the content has loaded:
<template>
<div>
<Spinner v-if="isLoading"/>
<div v-else>
... actual dom content
</div>
</div>
</template>
<script>
export default {
data () {
return {
isLoading: true
}
},
mounted () {
this.$nextTick(() => {
this.isLoading = false
})
}
}
</script>
Target
On click "Open menu" button:
Dim overlay appearing with fade-in animation
Once dim overlay animation done, from the top, dim overlay is appearing with the sliding animation from the top to bottom:
Solution attempt and problem
<template lang="pug">
transition(name="fade")
.DrawerMenu-DimUnderlay(v-if="displayFlag")
.DrawerMenu-Body Drawer menu
</template>
Before slide down the .DrawerMenu-Body, .DrawerMenu-DimUnderlay must be mounted and rendered.
I don't know how to implement it.
🌎 Fiddle
You can achieve that by using CSS Animations and Vue Transition.
First, separate your overlay and content into different transitions:
<template lang="pug">
div
transition(name="overlay")
.DrawerMenu-Overlay(v-if="displayFlag")
transition(name="content")
.DrawerMenu-Body(v-if="displayFlag") Drawer menu
</template>
Then define your animations:
.DrawerMenu {
&-Overlay {
...
display: none;
}
...
}
.overlay-enter-active {
display: block;
animation: fade-in-and-slide-down 2s;
}
.content-enter-active {
animation: wait-and-fade-in 3s;
}
.content-leave-active {
animation: fade-out 1s;
}
#keyframes fade-in-and-slide-down {
0% {
opacity: 0;
}
50% {
opacity: 1;
transform: translateY(0);
}
100% {
transform: translateY(100%);
}
}
#keyframes wait-and-fade-in {
0% {
opacity: 0;
}
66% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
Example in CodeSandbox.
Another solution is using JavaScript animation library (such as animejs) combine with Vue Transition Hooks. I would prefer this solution for a complex animation.
<template lang="pug">
transition(#enter='enter' #leave='leave')
div(v-if='displayFlag')
.DrawerMenu-Overlay(ref='overlay')
.DrawerMenu-Body(ref='content' #click="displayFlag = false") Drawer menu
</template>
import anime from "animejs";
...
methods: {
enter(el, done) {
anime
.timeline({
easing: "linear",
duration: 1000,
complete: done
})
.add({
targets: this.$refs.overlay,
opacity: [0, 1]
})
.add({
targets: this.$refs.overlay,
translateY: "100%"
})
.add({
targets: this.$refs.content,
opacity: [0, 1]
});
},
leave(el, done) {
anime({
targets: el,
duration: 2000,
opacity: 0,
complete: done
});
},
...
}
...
You can also use without transition component but you have to handle v-if variable by yourself.
Example in CodeSandbox.
Not sure if there are 2 questions here, but for your last question, I would say that is because that ref component does not have a property display.
It does however, have a function display()
Therefore, change your button click to this:
<button #click="$refs.drawerMenu.display()">Open menu</button>
Since I am new to Vue and JS. I have some difficulties to making dynamic progress bar. This bar is kind of indication of how many quiz already take. according to he photo below.
Below is my CSS and HTML of creating bar.
.progressbar {
width: 100%;
height: 5px;
background-color: #eee;
margin: 1em auto;
transition: width 500ms;
}
HTML
<div class="progressbar">
<div class="progressbar text-center"
style="background-color: green; margin: 0; color: white;"
:style="{width: progress + '%'}">
{{ progress }}
</div>
</div>
how can I make it increase?
Place progress into the data and its working for me:
data() {
return {
progress: 70,
}
}
If you don't know how to calculate the progress just do it like so:
methods: {
updateProgres() {
this.progress=completedSteps/totalSteps*100
}
}