Using CSS transforms for micro-interactions in VUE3

Home Code Using CSS transforms for micro-interactions in VUE3

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.

Add the component to the application

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();
    }
});                        

Add the component to a HTML page

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>                        

Tech Stack

PHP

HTML

Javascript

C#

CSS