JSFiddle - React, Tailwind, and code Playground
by Nehemia Mwansasu
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<h1 class="title">Hover over the cards</h1>
<div id="app" class="container">
<card data-image="https://www.info.surveystars.com/assets/public/images/feature-images/workflow_surveystars.jpg?dpr=2&auto=compress,format&fit=crop&w=1199&h=798&q=80&cs=tinysrgb&crop=">
<h1 slot="header">Marketing</h1>
<p slot="content">Manage your company with Real-Time Intelligence <a href="https://www.info.surveystars.com/managing" target="_blank">Explore Managing Feature</a></p>
</card>
<card data-image="https://www.info.surveystars.com/assets/public/images/feature-images/operationsmanagement_surveystars.jpg?dpr=2&auto=compress,format&fit=crop&w=1199&h=799&q=80&cs=tinysrgb&crop=">
<h1 slot="header">Marketing</h1>
<p slot="content">Market with High Touch & Responsiveness </br> <a href="https://www.info.surveystars.com/marketing" target="_blank">Explore Marketing Feature</a></p>
</card>
<card data-image="https://www.info.surveystars.com/assets/public/images/feature-images/teamwork_surevystars_pic.jpg?dpr=2&auto=compress,format&fit=crop&w=1199&h=799&q=80&cs=tinysrgb&crop=">
<h1 slot="header">Ordering</h1>
<p slot="content">Utilize a More Efficient Ordering Process </br> <a href="https://www.info.surveystars.com/ordering" target="_blank">Explore Ordering Feature</a></p>
</card>
<card data-image="https://www.info.surveystars.com/assets/public/images/feature-images/SS%20Team%20Meeting.jpg?dpr=2&auto=compress,format&fit=crop&w=1199&h=811&q=80&cs=tinysrgb&crop=">
<h1 slot="header">Researching</h1>
<p slot="content">Researching is Mission Critical, But Make it Easy and Quick, One-Click with Stars!</br> <a href="https://www.info.surveystars.com/researching" target="_blank">Explore Research Feature</a></p>
</card>
<card...
CSS
body {
margin: 40px 0;
font-family: "Raleway";
font-size: 14px;
font-weight: 500;
background-color: #212121;
-webkit-font-smoothing: antialiased;
}
a {
color: #3366ff;
background: #212121;
border-radius: 3px;
padding: 3px;
}
.title {
font-family: "Raleway";
font-size: 24px;
font-weight: 700;
color: #fff;
text-align: center;
}
p {
line-height: 1.5em;
}
h1 + p, p + p {
margin-top: 10px;
}
.container {
padding: 40px 80px;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.card-wrap {
margin: 10px;
-webkit-transform: perspective(800px);
transform: perspective(800px);
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
cursor: pointer;
}
.card-wrap:hover .card-info {
-webkit-transform: translateY(0);
transform: translateY(0);
}
.card-wrap:hover .card-info p {
opacity: 1;
}
.card-wrap:hover .card-info, .card-wrap:hover .card-info p {
transition: 0.6s cubic-bezier(0.23, 1, 0.32, 1);
}
.card-wrap:hover .card-info:after {
transition: 5s cubic-bezier(0.23, 1, 0.32, 1);
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0);
}
.card-wrap:hover .card-bg {
transition: 0.6s cubic-bezier(0.23, 1, 0.32, 1), opacity 5s cubic-bezier(0.23, 1, 0.32, 1);
opacity: 0.8;
}
.card-wrap:hover .card {
transition: 0.6s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 2s cubic-bezier(0.23, 1, 0.32, 1);
box-shadow: rgba(255, 255, 255, 0.2) 0 0 40px 5px, white 0 0 0 1px, rgba(0, 0, 0, 0.66) 0 30px 60px 0, inset #333 0 0 0 5px, inset white 0 0 0 6px;
}
.card {
position: relative;
flex: 0 0 240px;
width: 240px;
height: 320px;
background-color: #333;
overflow: hidden;
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.66) 0 30px 60px 0, inset #333 0 0 0 5px, inset rgba(255, 255, 255, 0.5) 0 0 0 6px;
transition: 1s cubic-bezier(0.445, 0.05, 0.55, 0.95);
}
.card-bg {
opacity: 0.5;
position: absolute;
top: -20px;
left: -20px;
...
JavaScript
Vue.config.devtools = true;
Vue.component('card', {
template: `
<div class="card-wrap"
@mousemove="handleMouseMove"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
ref="card">
<div class="card"
:style="cardStyle">
<div class="card-bg" :style="[cardBgTransform, cardBgImage]"></div>
<div class="card-info">
<slot name="header"></slot>
<slot name="content"></slot>
</div>
</div>
</div>`,
mounted() {
this.width = this.$refs.card.offsetWidth;
this.height = this.$refs.card.offsetHeight;
},
props: ['dataImage'],
data: () => ({
width: 0,
height: 0,
mouseX: 0,
mouseY: 0,
mouseLeaveDelay: null
}),
computed: {
mousePX() {
return this.mouseX / this.width;
},
mousePY() {
return this.mouseY / this.height;
},
cardStyle() {
const rX = this.mousePX * 30;
const rY = this.mousePY * -30;
return {
transform: `rotateY(${rX}deg) rotateX(${rY}deg)`
};
},
cardBgTransform() {
const tX = this.mousePX * -40;
const tY = this.mousePY * -40;
return {
transform: `translateX(${tX}px) translateY(${tY}px)`
}
},
cardBgImage() {
return {
backgroundImage: `url(${this.dataImage})`
}
}
},
methods: {
handleMouseMove(e) {
this.mouseX = e.pageX - this.$refs.card.offsetLeft - this.width/2;
this.mouseY = e.pageY - this.$refs.card.offsetTop - this.height/2;
},
handleMouseEnter() {
clearTimeout(this.mouseLeaveDelay);
},
handleMouseLeave() {
this.mouseLeaveDelay = setTimeout(()=>{
this.mouseX = 0;
this.mouseY = 0;
}, 1000);
}
}
});
const app = new Vue({
el: '#app'
});