A multi-item card carousel in Vue. It was fun thinking about how this should be viewed from the perspective of a component. It basically takes a window size and increases/decreases the pagination window on each click, which updates a style which translates the images. Values are hardcoded for demonstration but it could be made generic via props :)
Created
March 3, 2021 13:17
-
-
Save myChen-skr/9d0a120fc30efaa76603301ab23df911 to your computer and use it in GitHub Desktop.
Vue card carousel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
link(href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400" rel="stylesheet") | |
h1 Vue Carousel | |
script#v-carousel(type="x/template") | |
.card-carousel-wrapper | |
.card-carousel--nav__left( | |
@click="moveCarousel(-1)" | |
:disabled="atHeadOfList" | |
) | |
.card-carousel | |
.card-carousel--overflow-container | |
.card-carousel-cards(:style="{ transform: 'translateX' + '(' + currentOffset + 'px' + ')'}") | |
.card-carousel--card(v-for="item in items") | |
img(src="https://placehold.it/200x200") | |
.card-carousel--card--footer | |
p {{ item.name }} | |
p.tag(v-for="(tag,index) in item.tag" :class="index > 0 ? 'secondary' : ''") {{ tag }} | |
.card-carousel--nav__right( | |
@click="moveCarousel(1)" | |
:disabled="atEndOfList" | |
) | |
#app | |
carousel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Vue.component("carousel", { | |
template: "#v-carousel", | |
data() { | |
return { | |
currentOffset: 0, | |
windowSize: 3, | |
paginationFactor: 220, | |
items: [ | |
{name: 'Kin Khao', tag: ["Thai"]}, | |
{name: 'Jū-Ni', tag: ["Sushi", "Japanese", "$$$$"]}, | |
{name: 'Delfina', tag: ["Pizza", "Casual"]}, | |
{name: 'San Tung', tag: ["Chinese", "$$"]}, | |
{name: 'Anchor Oyster Bar', tag: ["Seafood", "Cioppino"]}, | |
{name: 'Locanda', tag: ["Italian"]}, | |
{name: 'Garden Creamery', tag: ["Ice cream"]}, | |
] | |
} | |
}, | |
computed: { | |
atEndOfList() { | |
return this.currentOffset <= (this.paginationFactor * -1) * (this.items.length - this.windowSize); | |
}, | |
atHeadOfList() { | |
return this.currentOffset === 0; | |
}, | |
}, | |
methods: { | |
moveCarousel(direction) { | |
// Find a more elegant way to express the :style. consider using props to make it truly generic | |
if (direction === 1 && !this.atEndOfList) { | |
this.currentOffset -= this.paginationFactor; | |
} else if (direction === -1 && !this.atHeadOfList) { | |
this.currentOffset += this.paginationFactor; | |
} | |
}, | |
} | |
}); | |
new Vue({ | |
el:"#app" | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$vue-navy: #2c3e50; | |
$vue-navy-light: #3a5169; | |
$vue-teal: #42b883; | |
$vue-teal-light: #42b983; | |
$gray: #666a73; | |
$light-gray: #f8f8f8; | |
body { | |
background: $light-gray; | |
color: $vue-navy; | |
font-family: 'Source Sans Pro', sans-serif; | |
} | |
.card-carousel-wrapper { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
margin: 20px 0 40px; | |
color: $gray; | |
} | |
.card-carousel { | |
display: flex; | |
justify-content: center; | |
width: 640px; | |
&--overflow-container { | |
overflow: hidden; | |
} | |
&--nav__left, | |
&--nav__right { | |
display: inline-block; | |
width: 15px; | |
height: 15px; | |
padding: 10px; | |
box-sizing: border-box; | |
border-top: 2px solid $vue-teal; | |
border-right: 2px solid $vue-teal; | |
cursor: pointer; | |
margin: 0 20px; | |
transition: transform 150ms linear; | |
&[disabled] { | |
opacity: 0.2; | |
border-color: black; | |
} | |
} | |
&--nav__left { | |
transform: rotate(-135deg); | |
&:active { | |
transform: rotate(-135deg) scale(0.9); | |
} | |
} | |
&--nav__right { | |
transform: rotate(45deg); | |
&:active { | |
transform: rotate(45deg) scale(0.9); | |
} | |
} | |
} | |
.card-carousel-cards { | |
display: flex; | |
transition: transform 150ms ease-out; | |
transform: translatex(0px); | |
.card-carousel--card { | |
margin: 0 10px; | |
cursor: pointer; | |
box-shadow: 0 4px 15px 0 rgba(40,44,53,.06), 0 2px 2px 0 rgba(40,44,53,.08); | |
background-color: #fff; | |
border-radius: 4px; | |
z-index: 3; | |
margin-bottom: 2px; | |
&:first-child { | |
margin-left: 0; | |
} | |
&:last-child { | |
margin-right: 0; | |
} | |
img { | |
vertical-align: bottom; | |
border-top-left-radius: 4px; | |
border-top-right-radius: 4px; | |
transition: opacity 150ms linear; | |
user-select: none; | |
&:hover { | |
opacity: 0.5; | |
} | |
} | |
&--footer { | |
border-top: 0; | |
padding: 7px 15px; | |
p { | |
padding: 3px 0; | |
margin: 0; | |
margin-bottom: 2px; | |
font-size: 19px; | |
font-weight: 500; | |
color: $vue-navy; | |
user-select: none; | |
&.tag { | |
font-size: 11px; | |
font-weight: 300; | |
padding: 4px; | |
background: rgba(40,44,53,.06); | |
display: inline-block; | |
position: relative; | |
margin-left: 4px; | |
color: $gray; | |
&:before { | |
content:""; | |
float:left; | |
position:absolute; | |
top:0; | |
left: -12px; | |
width:0; | |
height:0; | |
border-color:transparent rgba(40,44,53,.06) transparent transparent; | |
border-style:solid; | |
border-width:8px 12px 12px 0; | |
} | |
&.secondary { | |
margin-left: 0; | |
border-left: 1.45px dashed white; | |
&:before { | |
display: none !important; | |
} | |
} | |
&:after { | |
content:""; | |
position:absolute; | |
top:8px; | |
left:-3px; | |
float:left; | |
width:4px; | |
height:4px; | |
border-radius: 2px; | |
background: white; | |
box-shadow:-0px -0px 0px #004977; | |
} | |
} | |
} | |
} | |
} | |
} | |
h1 { | |
font-size: 3.6em; | |
font-weight: 100; | |
text-align: center; | |
margin-bottom: 0; | |
color: $vue-teal; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment