I've seen some advertisements of Wix that show a few perspective tricks in CSS that look impressive. Now I would never use Wix myself, but whatever fancy bells and whistles are to be found in such services are usually fun things to try and replicate. Here's a simple code you can use to make a micro-interaction that adds a 3D illusion to an element by messing with it's transform styles in realtime.
Below is the actual component code. Add this to your application first.
/*
* The card is a visual component. It reads mouse events and applies a transform
* to the inner div style, resulting in a 3D micro-interaction.
*
* PROPS
* degrees: the maximum amount of degrees applied to the transform.
* perspective: the perspective applied to the transform.
* invert: inverts the mouse controls (optional).
* image: the image loaded into the background (optional).
*
* SLOTS
* html: the inner HTML of the card.
*/
app.component('flex-card',
{
name:"Flex Card",
props: {degrees:{type:Number,default:20},perspective:{type:Number,default:1200},inverted:{type:Boolean,default:false},image:String,background:{type:Boolean,default:true}},
data() {
return {
event:null,
hover:false,
rotateX:0,
rotateY:0,
style:{transform:'perspective(0px) rotate3d(0,0,0,0deg)'},
}
},
methods: {
// on mouse enter the event will be set.
enter:function(event){
this.event = event;
this.hover = true;
this.setTransform();
},
// on mouse leave the event will be set, and all values will be reset.
leave:function(event){
this.event = event;
this.hover = false;
this.rotateX = 0;
this.rotateY = 0;
this.setTransform();
},
// on mouse move the event will be set, and all values will be (re)calculated.
move:function(e){
this.event = e;
var rect = this.$el.getBoundingClientRect();
var posX = this.event.clientX - rect.x;
var posY = this.event.clientY - rect.y;
var percX = ( (posX / rect.width) - 0.5) * 2;
var percY = ( (posY / rect.height) - 0.5) * 2;
if (this.$props.inverted){
this.rotateX = percX * -1;
this.rotateY = percY;
} else {
this.rotateX = percX;
this.rotateY = percY * -1;
}
this.setTransform();
},
// sets the actual transform on the style of the element.
setTransform:function(){
this.style.transform = "perspective("+this.$props.perspective+"px) rotate3d("+this.rotateY+","+this.rotateX+",0,"+this.$props.degrees+"deg)";
},
// sets the image when one is provided.
setImage:function(){
if (this.$props.image) {
var imageElement = this.$el.getElementsByClassName("inner")[0].getElementsByClassName("img")[0];
imageElement.style.backgroundImage = "url('"+this.$props.image+"')";
}
}
},
template:
`
<div class="flex-card" @mouseover="enter($event)" @mouseleave="leave($event)" @mousemove="move($event)">
<div class="inner" v-bind:style="style">
<div v-if="$props.image" class="img"></div>
<div v-if="$props.background" class="background"></div>
<slot name="html"></slot>
</div>
</div>
`,
mounted:function(){
this.setImage();
}
});
All we have to do now is render the component somewhere:
<div class='layout'>
<div class='content'>
<div class='clients'>
<?php foreach($pages->find("template=page-client,limit=3") as $client) : ?>
<div class='client'>
<?php
$imgUrl = ''; if ($client->image_gallery->count > 0) {$imgUrl = $client->image_gallery->getRandom()->width(500)->url; }
$markup = '<div class="client-heading"><h2><a href="'.$client->url.'">'.$client->title.'</a></h2></div>';
$markup .= '<div class="body">'.$client->body.'</div>';
$markup .= '<div class="buttons"><a href="'.$client->url.'">Read More</a><a href="'.$client->url_website.'" target="_blank">Visit Website</a></div>';
?>
<flex-card title="<?php echo $client->title; ?>" desc="<?php echo $client->name; ?>" :degrees=20 :perspective=1200 :inverted="false" image="<?php echo $imgUrl; ?>" :background="true">
<template v-slot:html><?php echo $markup; ?></template>
</flex-card>
<div style="opacity:0;pointer-events:none;">
<?php echo $markup; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>