Create component with CSS class depending on props given - css

Is there a way in Vue.js to create a component that is containing CSS class scoped on which some values are given as parameters (props) before his creation.
Like:
<BeautifulBar :colorSelected="red" :colorDefault="blue", :animDuration="10.0s"></BeautifulBar
And in the component itself:
<template>
<div :class="'bar ' + (selected ? 'selected animated' : 'default')" #click="select()">The Bar</div>
</template>
<script>
export default{
data: function () { return { selected: false } },
props: {
colorSelected : {default: "orange", type: String},
colorDefault: {default: "orange", type: String},
animDuration: {default: "4.0s", type: String},
},
method: {
select: function() { this.selected = !this.selected;}
}
}
</script>
/* HERE */
<style scoped>
selected: {
color: red,
background-color: var(this.colorSelected)
}
default: {
color: black,
background-color: var(this.colorDefault)
}
animate:{
-webkit-transition: var(animDuration) !important;
-moz-transition: var() !important;
-o-transition: var() !important;
transition: var() !important;
}
</style>

Have you tried using computed function for class?
computed:{
classObject:function(){
return {
'selected': this.selected,
'animated': this.selected,
'default': !this.selected
}
}
}
then
<div :class="classObject">
your template
</div>

I succeed doing it like this (showing a progress bar looping animation, you can pass the animation time as props):
<template>
<div class="progress" :style="`height: ${height}rem;`">
<div
:class="'progress-bar bg-success ' + 'progressAnimation'"
role="progressbar"
:style="`width: ${progressValue}%; --animationDuration: ${animationDurationStr};`"
:aria-valuenow="`${progressValue}`"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
</template>
<script scoped>
import config from "../config";
export default {
props: {
animationDuration: { type: Number, default: 140 },
height: { type: Number, default: 0.25 }
},
created: function() {
config.log("------------ AutoLoopProgressBar -----------");
config.log(
"autoloopprogressbar",
"Set animation duration to " + this.animationDurationStr
);
},
mounted: function() {
config.log("autoloopprogressbar", "Setup timer");
setInterval(() => {
this.reset();
}, this.animationDuration * 1000);
this.reset();
},
data: function() {
return {
progressValue: 0
};
},
computed: {
animationDurationStr: function() {
return this.animationDuration + ".0s";
}
},
methods: {
reset: function() {
let tmp = this.animationDuration;
this.animationDuration = 0;
this.progressValue = 0;
setTimeout(() => {
this.animationDuration = tmp;
this.progressValue = 100;
}, 0);
}
}
};
</script>
<style scoped>
.progressAnimation {
--animationDuration: 1s;
-webkit-transition: var(--animationDuration) linear !important;
-moz-transition: var(--animationDuration) linear !important;
-o-transition: var(--animationDuration) linear !important;
transition: var(--animationDuration) linear !important;
}
</style>

Related

vuetify: how to make v-data-table row blink when item values are updated

I would like to create a table that when items is updated, the row blink once.
i managed to make a row blink when the component starts, but it does not blink when value is updated.
i created an example with two css class (only for test), one that blinks once and another that blinks infinite.
if we update items values, we can see that the infinite still blinks and change rows as the condition is filled, but the items that should blink once, didn't change.
any help will be appreciated.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
items: [{
id: 1,
name: 'Frozen Yogurt',
calories: 159,
},
{
id: 2,
name: 'Ice cream sandwich',
calories: 237,
},
{
id: 3,
name: 'Eclair',
calories: 262,
},
{
id: 4,
name: 'Cupcake',
calories: 305,
},
],
headers: [{
text: 'Dessert',
value: 'name',
},
{
text: 'Calories',
value: 'calories'
},
],
};
},
methods: {
blink(item) {
if (item.calories > 200){
return 'blink-great';
} else {
return 'blink-less';
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
},
updateValues() {
const newValues = this.items.map((el) => {
return {...el, calories: this.getRandomInt(100,500)};
})
this.items = newValues
}
},
computed: {
displayItems() {
var newItems = this.items.map((el) => {
return {...el, calories: el.calories * 1};
})
return newItems
}
},
});
.blink-less {
animation: blinking ease-out 1s 3;
--background-color: #FF0000
}
.blink-great {
animation: blinking ease-out 1s infinite;
--background-color: #0000FF
}
#keyframes blinking {
0% {
background-color: var(--background-color);
}
100% {
background-color: #fff;
}
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="pa-5">
<v-btn
#click="updateValues()"
> Update value </v-btn>
</v-row>
<v-row class="px-5">
<v-data-table
hide-default-footer
:headers="headers"
:items="displayItems"
:item-class="blink"
/>
</v-row>
</v-container>
</v-app>
</div>
I created a flag and added new css classes.. it works but has poor code. If i found a better and clean solution i post it here
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
controlAnimation: false,
items: [{
id: 1,
name: 'Frozen Yogurt',
calories: 159,
},
{
id: 2,
name: 'Ice cream sandwich',
calories: 237,
},
{
id: 3,
name: 'Eclair',
calories: 262,
},
{
id: 4,
name: 'Cupcake',
calories: 305,
},
],
headers: [{
text: 'Dessert',
value: 'name',
},
{
text: 'Calories',
value: 'calories'
},
],
};
},
methods: {
blink(item) {
if (this.controlAnimation) {
let itemClassTrue = 'blink-even-true'
/* check item.value, based on condition...
itemClassTrue = 'blink-less-true'
itemClassTrue = 'blink-even-true'
itemClassTrue = 'blink-great-true'
*/
return itemClassTrue;
} else {
let itemClassFalse = 'blink-great-false'
/* check item.value, based on condition...
itemClassTrue = 'blink-less-false'
itemClassTrue = 'blink-even-false'
itemClassTrue = 'blink-great-false'
*/
return itemClassFalse;
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
},
updateValues() {
const newValues = this.items.map((el) => {
return {...el, calories: this.getRandomInt(100,500)};
})
this.items = newValues
}
},
computed: {
displayItems() {
this.controlAnimation = !this.controlAnimation
var newItems = this.items.map((el) => {
return {...el, calories: el.calories * 1};
})
return newItems
}
},
});
.blink-less-true {
animation: blinking-less-true 1s 2 !important;
--background-color: rgb(196, 76, 76) !important;
}
.blink-even-true {
animation: blinking-even-true 1s 2 !important;
--background-color: #fa0 !important;
}
.blink-great-true {
animation: blinking-great-true 1s 2 !important;
--background-color: rgb(3, 163, 70) !important;
}
.blink-less-false {
animation: blinking-less-false 1s 2 !important;
--background-color: rgb(196, 76, 76) !important;
}
.blink-even-false {
animation: blinking-even-false 1s 2 !important;
--background-color: #fa0 !important;
}
.blink-great-false {
animation: blinking-great-false 1s 2 !important;
--background-color: rgb(3, 163, 70) !important;
}
#keyframes blinking-less-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-even-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-great-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-less-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-even-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-less-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="pa-5">
<v-btn
#click="updateValues()"
> Update value </v-btn>
</v-row>
<v-row class="px-5">
<v-data-table
hide-default-footer
:headers="headers"
:items="displayItems"
:item-class="blink"
/>
</v-row>
</v-container>
</v-app>
</div>
You're not far off with item-class. It can take a string, which tells it to look for a prop on the item's object. With this, as you're updating the data, you can just add/remove the blink class to a prop you create on the fly.
In this case, I'm just going to name the prop dynamicClass
something like this:
template:
<v-data-table
hide-default-footer
:headers="headers"
:items="items"
:item-class="dynamicClass"
/>
script:
methods: {
updateRandom() {
const randomIdx = this.generateRandomNumber(0, this.items.length - 1);
const randomNumber = this.generateRandomNumber(1, 1200);
let item = { ...this.items[randomIdx] };
item.calories = randomNumber;
item.dynamicClass = "blink-less";
this.$set(this.items, randomIdx, item);
setTimeout(() => {
delete item.dynamicClass;
this.$set(this.items, randomIdx, item);
}, 2000);
},
generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
},
},
The idea here is that we are adding & removing the class from the item/object in the array. doing it with this.$set() guarantees reactivity. But you could also use Array.splice() but either way, you want to replace the data object at that index to maintain reactivity.
Here is a working example in a codesandbox

CSS/Vue.js synchronize animation reset with ref value change

I want to create an animation that fades between 3 states. To store these states (that contain an image and some text), I've created an array of objects (uploadSteps). I increment the index of this array at a specific interval to create an animation.
My problem comes with CSS, I would like to create a fade-out/fade-in effect between each step, so that the transition is more smooth. It works most of the time but sometimes, after a refresh and for the first transition, I think the CSS animation is restarted before the state is changed. I wasn't able to reproduce it constantly so far.
What it does => Image fade-in -> Image 1 -> Image 1 fade-Out -> Image 1 -> Image 2 fade-in
What I want => Image fade-in -> Image 1 -> Image 1 fade-Out -> Image 2 fade-in
If you pay attention you can see that there's kind of a blink effect at the transition between image 1 and 2. What could I do to solve this ? Help appreciated !
PS: I've included some videos to illustrate my issue
https://streamable.com/4aigjq - Animation KO
https://streamable.com/umu3nj - Animation OK
<template>
<div class="screenshot-upload-processing-container">
<div class="image-container">
<img
ref="imageRef"
:src="
currentStep ? $requireImage(currentStep.imagePath).toString() : ''
"
:alt="currentStep.text"
class="image"
/>
</div>
<div class="centered-row">
<span ref="textRef" class="text">{{
currentStep ? currentStep.text : ''
}}</span>
</div>
</div>
</template>
<script lang="ts">
import {
computed,
defineComponent,
onBeforeUnmount,
ref,
} from '#nuxtjs/composition-api';
export default defineComponent({
name: 'ScreenshotUploadProcessing',
setup() {
const stepNumber = ref(0);
const timer = ref<NodeJS.Timeout | null>(null);
const imageRef = ref<HTMLImageElement | null>(null);
const textRef = ref<HTMLSpanElement | null>(null);
const uploadSteps = computed(() => {
return [
{
imagePath: 'image1.svg',
text: 'Text 1',
},
{
imagePath: 'image2.svg',
text: 'Text 2',
},
{
imagePath: 'image3.svg',
text: 'Text 3',
},
];
});
const resetAnimations = () => {
const image = imageRef.value;
const text = textRef.value;
if (image) {
image.style.animation = 'none';
void image.offsetHeight;
image.style.animation = '';
}
if (text) {
text.style.animation = 'none';
void text.offsetHeight;
text.style.animation = '';
}
};
const updateStepNumber = () => {
timer.value = setInterval(() => {
if (stepNumber.value === uploadSteps.value.length - 1) {
stepNumber.value = 0;
} else {
stepNumber.value++;
}
resetAnimations();
}, 3000);
};
onBeforeUnmount(() => {
clearInterval(timer.value as NodeJS.Timeout);
});
const currentStep = computed(() => uploadSteps.value[stepNumber.value]);
updateStepNumber();
return {
stepNumber,
currentStep,
imageRef,
textRef,
uploadSteps,
};
},
});
</script>
<style lang="scss" scoped>
#keyframes fade {
0%,
100% {
opacity: 0;
}
40%,
60% {
opacity: 1;
}
}
.screenshot-upload-processing-container {
display: flex;
flex-direction: column;
height: 100%;
}
.image {
animation: fade 3s linear;
}
.image-container {
display: flex;
align-items: center;
justify-content: center;
height: 27rem;
margin-top: 10.3rem;
}
.centered-row {
display: flex;
align-items: center;
justify-content: center;
margin-top: $spacing-xl;
}
.text {
#extend %text-gradient-1;
animation: fade 3s linear;
font-weight: $font-weight-medium;
}
</style>

Font awesome icon in Vue.js does not display

I am trying to add a font-awesome arrow icon via my css code like this:
<style>
.negative {
color: red;
}
.positive {
color: green;
}
.negative::after {
content: "\f106";
}
</style>
I have font-awesome included in my html via CDN. For some reason my icon does not display properly, it just shows a square. Any ideas why and how I can fix it?
Here is the rest of my code, showing the logic behind the displaying of percentages:
<template>
<div>
<v-data-table
:headers="headers"
:items="rowsToDisplay"
:hide-default-footer="true"
class="primary"
>
<template #item.thirtyDaysDiff="{ item }">
<span :class="item.thirtyDaysDiffClass">{{ item.thirtyDaysDiff }}%</span>
</template>
<template #item.sevenDaysDifference="{ item }">
<span :class="item.sevenDaysDiffClass">{{ item.sevenDaysDiff }}%</span>
</template>
</v-data-table>
</div>
</template>
<script>
import axios from 'axios';
export default {
data () {
return {
bitcoinInfo: [],
isPositive: false,
isNegative: false,
headers: [
{
text: 'Currency',
align: 'start',
value: 'currency',
},
{ text: '30 Days Ago', value: '30d' },
{ text: '30 Day Diff %', value: 'thirtyDaysDiff'},
{ text: '7 Days Ago', value: '7d' },
{ text: '7 Day Diff %', value: 'sevenDaysDifference' },
{ text: '24 Hours Ago', value: '24h' },
],
}
},
methods: {
getBitcoinData() {
axios
.get('data.json')
.then((response => {
var convertedCollection = Object.keys(response.data).map(key => {
return {currency: key, thirtyDaysDiff: 0, sevenDaysDifference: 0, ...response.data[key]}
})
this.bitcoinInfo = convertedCollection
}))
.catch(err => console.log(err))
},
calculateDifference(a, b) {
let calculatedPercent = 100 * Math.abs((a - b) / ((a + b) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
},
getDiffClass(a, b) {
return a > b ? 'positive': a < b ? 'negative' : ''
},
calculateSevenDayDifference(item) {
let calculatedPercent = 100 * Math.abs((item['24h'] - item['7d']) / ((item['24h'] + item['7d']) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
}
},
computed: {
rowsToDisplay() {
return Object.keys(this.bitcoinInfo)
.map(key => {
return {
currency: key,
...this.bitcoinInfo[key]
}
}).map((item) => ({
...item,
thirtyDaysDiff: this.calculateDifference(item['7d'], item['30d']),
thirtyDaysDiffClass: this.getDiffClass(item['7d'], item['30d']),
sevenDaysDiff: this.calculateDifference(item['24h'], item['7d']),
sevenDaysDiffClass: this.getDiffClass(item['24h'], item['7d']),
}))
}
},
mounted() {
this.getBitcoinData()
}
}
</script>
have you tried to import your icons inside the template area with <i ...></i>?
here is an working example. Check out the cdn.fontawesome/help-page to get more information.
Vue.createApp({
data () {
return {
isPositive: false,
isNegative: true
}
}
}).mount('#demo')
.negative {
color: red;
}
.positive {
color: green;
}
.neutral {
color: #666;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#next"></script>
<div id="demo">
<i class="fas" :class="isPositive ? 'fa-angle-up positive' : isNegative ? 'fa-angle-down negative' : 'fa-minus neutral' "></i>
<br>
<br>
<button #click="isPositive = !isPositive; isNegative = !isNegative" v-text="'change pos and neg'" />
</div>
so basically you'll bind the icon classes to your own conditions. You could write the conditions for example with the tenary operator into your template area. Hope you get the idea.
Host Fontawesome yourself by following the steps in this Fontawesome documentation.
https://fontawesome.com/docs/web/setup/host-yourself/webfonts
i hope this help.

React CSS: trying to give Color to class for a few seconds when there is a change on state vs prevState

Goal: I want to compare prevState with actualState to display a different class on a <div> depending on the comparision.
Problem: When the iteration results in no switch of class,i cant see the difference since the className doesnt trigger the keyframe event.
JSX:
import React, { useEffect, useState, useRef } from "react";
import styles from "./CoinContainer.module.css";
function usePrevious(data) {
const ref = useRef();
useEffect(() => {
ref.current = data;
}, [data]);
return ref.current;
}
export default function CoinContainer({ coin, price }) {
const [priceUpdated, setPrice] = useState("");
const prevPrice = usePrevious(priceUpdated);
useEffect(() => {
setInterval(priceUpdate, 20000);
});
const toggleClassName = () => {
return prevPrice > priceUpdated ? styles.redPrice : styles.greenPrice;
};
function priceUpdate() {
return fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${coin}&vs_currencies=usd`
)
.then((data) => data.json())
.then((result) => {
let key = Object.keys(result);
setPrice(result[key].usd);
});
}
return (
<div className={styles.padding}>
<h2>{coin}</h2>
<h3 className={toggleClassName()}>
{priceUpdated ? priceUpdated : price}$
</h3>
</div>
);
}
CSS:
#keyframes upFadeBetween {
from {
color: green;
}
to {
color: white;
}
}
#keyframes downFadeBetween {
from {
color: red;
}
to {
color: white;
}
}
.redPrice {
color: white;
animation: downFadeBetween 5s;
}
.greenPrice {
color: white;
animation: upFadeBetween 5s;
}
Thanks so much for any feedback/help!

Vue Carousel on iOS Safari only works with first slide

Good morning, I've build a custom vue carousel and implemented it into my progressive web app. Works fine on Chrome Android, whereas on iOS Safari - id doesn't. I received feedback that the carousel only allows to slide the first page and on the second - it doesn't react.
The problem is, I don't own Mac or Iphone and for now I can't test it myself. Could someone help me out and test the carousel on aforementioned devices? Here is the fiddle.
https://jsfiddle.net/LSliwa/97cpgq3z/
HTML:
<div id="app">
<Carousel>
<div v-for="(todo, index) in todos" :key="index">
<div class="app__element">
{{ todo.text }}
</div>
</div>
</Carousel>
</div>
CSS:
#app {
padding-top: 1rem;
}
.app__element {
text-align: center;
border: 1px solid red;
padding: 1rem;
margin: 0 1rem;
}
/* carousel styles */
.carousel {
overflow-x: hidden;
overflow-y: visible;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.carousel-navdots {
display: flex;
justify-content: center;
margin-bottom: 2rem;
}
.carousel-navdot {
height: 10px;
width: 10px;
border-radius: 50%;
background-color: gray;
margin: 0 5px;
transition: all 0.5s;
cursor: pointer;
}
.active {
background-color: #05AA19;
}
.carousel-wrapper {
display: flex;
align-items: stretch;
cursor: grab;
}
.carousel-wrapper:active {
cursor: grabbing;
}
.carousel-wrapper>div,
.carousel-wrapper>p,
.carousel-wrapper>span,
.carousel-wrapper>ul {
width: 100%;
flex-shrink: 0;
position: relative;
}
.scrolling {
transition: transform 0.5s;
}
.inactive {
flex-direction: column;
}
#media (min-width: 1024px) {
.inactive {
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
}
}
JS:
Vue.component('Carousel', {
template: '<div class="carousel"><div class="carousel-navdots" v-if="isActive" v-show="pagination"><div class="carousel-navdot" :class="{ \'active\': n == currentPage + 1 }" v-for="n in numberOfPages" v-show="numberOfPages > 1" :key="n" ref="navdot" #click="scrollWithNavdots(n)"></div></div><div class="carousel-wrapper a-stretch" :class="{ \'inactive\': !isActive }" :style="{ transform: `translateX(${translate}px)` }" ref="wrapper" v-on="isActive && active ? { touchstart: onTouchStart, touchmove: onTouchMove, touchend: onTouchEnd, mousedown: onTouchStart, mousemove: onTouchMove, mouseup: onTouchEnd } : {}"><slot></slot></div></div>',
props: {
active: {
type: Boolean,
default: () => true
},
activeOnViewport: {
type: Array,
default: () => [[1, true]]
},
columns: {
type: Array,
default: () => [[1, 1]]
},
pagination: {
type: Boolean,
default: () => true
},
sensitivity: {
type: Number,
default: () => 40
},
startFromPage: {
type: Number,
default: () => 0
},
autoplay: {
type: Boolean,
default: () => false
},
autoplaySpeed: {
type: Number,
default: () => 5
},
// usuń custom length jeżeli rerendering z :key zadziała
customLength: {
type: Number
}
},
data() {
return {
currentPage: this.startFromPage,
numberOfColumns: 1,
moveStart: null,
move: null,
currentTranslate: 0,
length: this.customLength == null ? this.$slots.default.length : this.customLength,
viewportColumnsMatched: null,
isActive: null,
mousedown: false,
elementWidth: 0,
autoplayInterval: null,
animateTimeout: null,
test: {
touchmove: false,
touchstart: false,
}
}
},
computed: {
maxScrollLeft() {
return this.currentPage == 0 ? true : false;
},
maxScrollRight() {
return this.currentPage + 1 == this.numberOfPages ? true : false;
},
numberOfPages() {
return Math.ceil(this.length / this.numberOfColumns);
},
sortedColumns() {
return this.columns.sort((a, b) => {
return a[0] - b[0];
});
},
sortedActive() {
return this.activeOnViewport.sort((a, b) => {
return a[0] - b[0];
})
},
translate() {
return -this.elementWidth * this.currentPage
}
},
watch: {
currentPage() {
this.animateCarousel();
this.$emit('change-page', this.currentPage);
},
startFromPage() {
this.currentPage = this.startFromPage;
},
// usuń watch customLength jeżeli rerendering z :key zadziała
customLength() {
this.length = this.customLength;
if (this.currentPage > this.length - 1) {
this.currentPage = this.length - 1;
}
}
},
methods: {
animateCarousel() {
this.$refs.wrapper.classList.add('scrolling');
this.animateTimeout = setTimeout(() => {
this.$refs.wrapper.classList.remove('scrolling');
}, 500);
},
scrollWithNavdots(index) {
this.currentPage = index - 1;
this.currentTranslate = parseFloat(this.$refs.wrapper.style.transform.slice(11, -3));
},
onTouchStart() {
clearInterval(this.autoplayInterval);
if (event.type == 'touchstart') {
this.moveStart = event.touches[0].screenX
} else {
this.moveStart = event.screenX;
this.mousedown = true;
}
},
onTouchMove() {
let translate;
if (event.type == 'touchmove') {
this.move = event.touches[0].screenX - this.moveStart;
} else if (event.type == 'mousemove' && this.mousedown == true) {
this.move = event.screenX - this.moveStart
}
if (this.move < 0 && this.maxScrollRight || this.move > 0 && this.maxScrollLeft) {
translate = this.translate + this.move*0.2;
} else {
translate = this.translate + this.move*0.5;
}
this.$refs.wrapper.style.transform = `translateX(${translate}px)`;
},
onTouchEnd() {
this.test.touchstart = false;
this.test.touchmove = false;
if (Math.abs(this.move) > this.sensitivity) {
if (this.move > 0 && !this.maxScrollLeft) {
this.currentPage--
} else if (this.move < 0 && !this.maxScrollRight) {
this.currentPage++
} else {
this.animateCarousel();
}
} else if (Math.abs(this.move) < this.sensitivity && Math.abs(this.move) > 1) {
this.animateCarousel();
}
this.$refs.wrapper.style.transform = `translateX(${this.translate}px)`;
this.mousedown = false;
this.moveStart = null;
this.move = null;
},
setColumns() {
this.viewportColumnsMatched = false;
this.sortedColumns.forEach(cur => {
if (window.matchMedia(`(min-width: ${cur[0]}px)`).matches) {
this.viewportColumnsMatched = true;
this.numberOfColumns = cur[1];
this.$refs.wrapper.childNodes.forEach(cur => {
cur.style.width = `${100/this.numberOfColumns}%`;
});
}
});
if (!this.viewportColumnsMatched) {
this.numberOfColumns = 1;
this.$refs.wrapper.childNodes.forEach(cur => {
cur.style.width = '100%';
});
}
setTimeout(() => {
this.elementWidth = this.$slots.default[0].elm.offsetWidth;
});
},
setActive() {
this.sortedActive.forEach(cur => {
if (window.matchMedia(`(min-width: ${cur[0]}px)`).matches) {
this.isActive = cur[1];
}
});
},
runCarousel() {
if (this.autoplay) {
this.autoplayInterval = setInterval(() => {
this.currentPage++
if (this.currentPage == this.numberOfPages) this.currentPage = 0;
}, this.autoplaySpeed * 1000);
}
},
},
mounted() {
this.setColumns();
this.setActive();
this.runCarousel();
window.addEventListener('resize', () => {
this.setColumns();
this.setActive();
});
},
destroyed() {
clearInterval(this.autoplayInterval);
clearTimeout(this.animateTimeout);
}
});
new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
})
Thank you in advance for your help, good people.

Resources