Using CSS transforms for micro-interactions in VUE3

HomeCodeUsing 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

Cookie Preferences

We only use depersonalized tracking cookies by Google Analytics and Google Tag Manager. We only use this tracking information to make improvements to the website and will not store any of your personal data in any functional cookies.