Learn how to achieve a sleek animation similar to the famous "Ken Burns effect" by utilizing the CSS property "transform" instead of "object-position". Check out the demo to see it in action!

I am currently exploring how to create an animation similar to the "Ken Burns" effect using CSS transform properties. While I have been using object-position to animate, I am facing challenges with the fluidity of the movement.

I am seeking help to achieve a smoother animation by utilizing CSS transform properties like translate or scale exclusively.

In addition, I have developed a demonstration of my current approach (or you can view it on Codepen):

body {
    display: flex;
}
.image-container {
    width: 150px;
    height: 150px;
    border-radius: 10px;
    overflow: hidden;
}
.object-position .image-container img {
    width: 150px;
    height: 150px;
    animation: kenBurns-object-position 20s linear infinite;
    object-fit: cover;
}
.transform .image-container img {
    width: 150px;
    height: 150px;
    animation: kenBurns-tranform 20s linear infinite;
    object-fit: cover;
}
@-webkit-keyframes kenBurns-object-position {
    0% {
        transform: scale(1.2);
        object-position: left top;
    }
    100% {
        transform: scale(1.0);
        object-position: center;
    }
}
@-webkit-keyframes kenBurns-tranform {
    0% {
        transform: scale(1.2) translate(-50px,0px);
    }
    100% {
        transform: scale(1.0) translate(0px,0px);
    }
}
<div class="object-position">
    <h1>Animation using "object-position"</h1>
    <label>image ratio: 1x1</label>
    <div class="image-container">
        <img src="https://source.unsplash.com/featured/500x500">
    </div>
    <label>image ratio: 2x1</label>
    <div class="image-container">
        <img src="https://source.unsplash.com/featured/1000x500">
    </div>
    <label>image ratio: 1x2</label>
    <div class="image-container">
        <img src="https://source.unsplash.com/featured/500x1000">
    </div>
</div>
<div class="transform">
    <h1>Animation using "transform"</h1>
    <label>image ratio: 1x1</label>
    <div class="image-container">
        <img src="https://source.unsplash.com/featured/500x500">
    </div>
    <label>image ratio: 2x1</label>
    <div class="image-container">
        <img src="https://source.unsplash.com/featured/1000x500">
    </div>
    <label>image ratio: 1x2</label>
    <div class="image-container">
        <img src="https://source.unsplash.com/featured/500x1000">
    </div>
</div>

Key considerations:

  • I require the animation to seamlessly adapt to images of different aspect ratios, whether horizontal, vertical, or square. The demo on CodePen features examples of all three formats as a visual aid.
  • The end goal is to replicate the effect achieved with object-fit but with a much smoother motion. Currently, the animation appears jittery, especially during longer durations (plans are for animations lasting between 40 and 60 seconds).

Thank you in advance for your time and support. Any assistance in tackling this challenge would be greatly appreciated.

Answer №1

Well, I was able to achieve my desired goal, although I might have gone a bit overboard...

If anyone could take a look at the code below and suggest improvements or simplifications, I would greatly appreciate it.

The main idea is that I am using javascript to parse images and assign them a class based on whether they are landscape, portrait, or square. I also extract the dimensions of the image and save them as variables for animation calculations...

One issue I'm still working on is the animation behavior when the image reaches the top or bottom for vertical images, or right and left for horizontal images. Due to the scale effect, the animation doesn't always touch the edge of the image. However, I decided to leave it like this to avoid adding more complexity and calculations...

function analyzeImages() {
    var images = document.querySelectorAll('.item-image');

    images.forEach(function(image) {
        var img = new Image();
        img.src = image.src;

        img.addEventListener('load', function() {
        var width = img.naturalWidth;
        var height = img.naturalHeight;
        var aspectRatio = width / height;
        var imageClass = '';
        var largeSize = '';
        var smallSize = '';

        if (aspectRatio > 1) {
            imageClass = 'landscape';
            largeSize = width + 'px';
            smallSize = height;
        } else if (aspectRatio < 1) {
            imageClass = 'portrait';
            largeSize = height + 'px';
            smallSize = width;
        } else {
            imageClass = 'square';
            largeSize = width + 'px';
            smallSize = height + 'px';
        }

        image.classList.add(imageClass);
        image.style.setProperty('--large-size', largeSize);
        image.style.setProperty('--small-size', smallSize);
        });
    });
}

document.addEventListener('DOMContentLoaded', function() {
analyzeImages();
});
body {
        display: flex;
        margin: 100px;
    }
    .image-container {
        width: 200px;
        height: 200px;
        border-radius: 10px;
        overflow: hidden;
        object-fit: cover;
    }
    .item-image.square {
        width: 200px;
        height: 200px;
        animation: kenBurns-square 30s linear infinite;
    }
    .item-image.landscape {
        height: 200px;
        animation: kenBurns-landscape 30s linear infinite;
    }
    .item-image.portrait {
        width: 200px;
        animation: kenBurns-portrait 30s linear infinite;
    }
    @-webkit-keyframes kenBurns-square {
        0% {
            transform: scale(1.3);
        }
        33% {
            transform: scale(1.0);
        }
        66% {
            transform: scale(1.0);
        }
        100% {
            transform: scale(1.3);
        }
    }
    @-webkit-keyframes kenBurns-portrait {
        0% {
            transform: translateY(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
        10% {
            transform: translateY(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
        20% {
            transform: scale(1.0) translateY(0px); /* top */
        }
        40% {
            transform: scale(1.3) translateY(calc((var(--large-size) / (var(--small-size) / 200) - 200px) * -1)); /* bottom */
        }
        60% {
            transform: scale(1.3) translateY(0px); /* top */
        }
        80% {
            transform: scale(1.0) translateY(calc((var(--large-size) / (var(--small-size) / 200) - 200px) * -1)); /* bottom */
        }
        90% {
            transform: translateY(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
        100% {
            transform: translateY(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
    }
    @-webkit-keyframes kenBurns-landscape {
        0% {
            transform: translateX(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
        10% {
            transform: translateX(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
        20% {
            transform: scale(1.0) translateX(0px); /* left */
        }
        40% {
            transform: scale(1.3) translateX(calc((var(--large-size) / (var(--small-size) / 200) - 200px) * -1)); /* right */
        }
        60% {
            transform: scale(1.3) translateX(0px); /* left */
        }
        80% {
            transform: scale(1.0) translateX(calc((var(--large-size) / (var(--small-size) / 200) - 200px) * -1)); /* right */
        }
        90% {
            transform: translateX(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
        100% {
            transform: translateX(calc((var(--large-size) / (var(--small-size) / 200) - 200px) / -2)); /* center */
        }
    }
<div class="image-container">
    <img class="item-image" src="https://source.unsplash.com/featured/500x500">
</div>
<div class="image-container">
    <img class="item-image" src="https://source.unsplash.com/featured/500x1000">
</div>
<div class="image-container">
    <img class="item-image" src="https://source.unsplash.com/featured/1000x500">
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Clicking the submit button in JavaScript will trigger a request to the Spring MVC backend,

When I use the following code, it gives me an object response: @RequestMapping(value = "/NewLogin",method = RequestMethod.POST) public @ResponseBody Token getAllBooks( Token token = new Token(); token.setValue(encryptedMessage); return toke ...

Custom AngularJS menu directive using a JSON file to generate submenus

As a newcomer to angularJs, I am looking to create a dynamic menu with submenus using a .json file. It's important for me to be able to easily add new menus and submenus through the same .json document. <a href="#" ng-repeat="item in menuHeader"&g ...

Each Vue instance on the page has its own isolated Vuex store

I have a situation where I am creating multiple Vue apps on the same page: import Vue from "vue"; import App from "./App.vue"; import useStore from "./store"; const arr = [1, 2]; for (const index of arr) { const store = use ...

What happens when a function returns an undefined value after completing an operation?

When the getObject() function is called, it returns an undefined value. function getObject(a) { return { x : a } } console.log(getObject()); ...

Is it possible to operate a jQuery mobile web application while disconnected?

Recently, I've been experimenting with the idea of creating a web application using jQuery Mobile that involves utilizing forms such as checkboxes, text fields, and combo boxes. The tasks associated with this app are quite simple, but they require dat ...

A guide on displaying data in a table using a select dropdown in a pug file and passing it to another pug file

After creating a single page featuring a select dropdown containing various book titles from a JSON file, I encountered an issue. Upon selecting a book and clicking submit (similar to this image), the intention was for it to redirect to another page named ...

utilize jquery ajax to input various data

I am attempting to include multiple data in a jQuery ajax call. However, my current implementation is not working as expected. The data fetched is taken from the following span: <span id="<?php echo $tutorial_id; ?>" modes="<?php echo $modese ...

Adjust the appearance of a glyphicon by modifying its color according to various criteria

Using angularjs, I implemented ng-repeat to display details in a table. In the final column, I aim to display the glyphicon 'glyphicon glyphicon-stop' in a specific color. <tr ng-repeat="ps in $ctrl.personLst"> <td>{{ ps.id}}</td& ...

Utilize Javascript or Jquery to intercept and handle both GET and POST requests

Is there a method to effectively intercept and capture both GET and POST requests as they are sent from the browser to the server? In my web application, full page refreshes occur after each request is submitted, however, some pages experience delays in r ...

Ways to effectively pass arguments to the callback function within the catch function in JavaScript

While working on my code, I suddenly felt the need to pass an extra argument, "msg", to the callback function renderError(). This extra argument should be passed along with the default error argument generated by the catch function itself. I tried doing i ...

What steps can be taken to ensure that the design of this page flows seamlessly?

Interested in a design similar to this https://i.sstatic.net/wbXo2.jpg <!DOCTYPE html> <html lang="en> <head> <meta charset="UTF-8> <title>Title</title> <style> html, body { height: 100%; margin: 0; ...

Using TypeScript, pass an image as a prop in a Styled Component

I am facing an issue with the code below that is supposed to display the "NoBillsLaptopPNG.src" image on the screen, but for some reason, the image is not showing up. The images are being imported correctly, so I'm unsure why the image is not appeari ...

Creating a dynamic HTML table by dynamically populating a column

I have limited experience with javascript, and I am working on a personal project that I enjoy. In the table below, you will find various location names along with their distances from the initial location. This serves as a travel companion for me to refe ...

Retrieve components of Node.js Express response using axios before terminating with end()

Is there a way to receive parts of a response from my nodejs server before res.end() using axios? Example: Server router.get('/bulkRes', (req,res)=>{ res.write("First"); setTimeout(()=>{ res.end("Done"); },5000); }) Cl ...

Display the HTML content retrieved from the SailsJS Controller

Exploring the world of SailsJS, I am on a mission to save HTML content in a database, retrieve it, and display it as rendered HTML. To achieve this goal, I have set up a sails model and a controller. This is what my model looks like: attributes: { ht ...

Tips for dynamically changing the number of visible ListItems in React using a single method

I recently stumbled upon the perfect solution at this link using material-ui. The chapter on "Nested list items" caught my attention, as it only has one nested item with a method for expanding more or less. In my sidebar, I have two nested items that both ...

Steps to display text in a div upon clicking on an image

I am trying to create an image with two DIVs separated by a black line. The left DIV will contain 4 images, and I want the following functionality: When a user clicks on any of the buttons in the left DIV, a corresponding text should be revealed in the ri ...

What are the steps to implement email validation, Saudi mobile number validation, and national ID validation in cshtml?

Looking to implement validations for the following fields: email, mobile number (must be 10 numbers and start with 05), and National ID (must be 10 numbers and start with 1 or 2) <input class="form-control" type="text" id="txt ...

Link the Material-ui ToggleButtonGroup with redux-form

I'm currently facing a challenge with connecting the material-ui ToggleButtonGroup to Redux form. The issue seems to be occurring in my code snippet below: <Field name='operator' component={FormToggleButtonGroup} > <ToggleButt ...

Error: The property 'open' is not defined in the mdMenu object

Encountered a bug while working with angular-material design... When using md-menu, if a sub menu item is opened (as shown in the image) and then hovering over a non-subMenu item (menu item), it throws an error "Cannot read property 'open' of nul ...