TransWikia.com

Created a paperfold like effect

Code Review Asked by Nico Shultz on November 25, 2021

I am trying to recreate a neat effect that I saw posted on Stack Overflow as a GIF .

I am trying to recreate this with CSS and JavaScript and I have created a similar effect with a button press.

I know there is a better way to position the divs since I am working with 3d moving and rotating. I need to have a formula that is using cos and sin to calculate the actual position it needs to be in. My math sadly isn’t that good so I calculate it by getting the actual height after the transform and with that I calculate the top value the next elements need to get.

const shiftAmount = 2;
const elements = document.querySelectorAll(".fold");
const elementBase = elements[0].getBoundingClientRect();
let currentState = "open";
let active = false;

let $foldStateLabel = $("#foldButton span");
document.onkeydown = function (e) {
    switch (e.which) {
        case 37: // left
            break;

        case 38: // up
            addDeg();
            break;

        case 39: // right
            break;

        case 40: // down
            minDeg();
            break;

        default: return; // exit this handler for other keys
    }
    e.preventDefault(); // prevent the default action (scroll / move caret)
};


$("#foldButton").click(function () {
    if (currentState == "open" && !active) {
        active = true;
        let interval = setInterval(function () {
            if (!minDeg()) {
                clearInterval(interval);
                currentState = "closed";
                active = false;
                $foldStateLabel.html("Out");
            }
        }, 10);
    } else if (currentState == "closed" && !active) {
        active = true;
        let interval = setInterval(function () {
            if (!addDeg()) {
                clearInterval(interval);
                currentState = "open";
                active = false;
                $foldStateLabel.html("In");
            }
        }, 10);
    }

});

let rotation = 0;


function addDeg() {
    let posDiff = 0;

    let origin;
    if (Math.abs(rotation) > 0) {

        rotation += shiftAmount;

        for (var i = 0; i < elements.length; i++) {
            let rot;
            elem = elements[i];
            $element = $(elem);
            if (i % 2 == 0) {
                origin = "top";
                rot = rotation;
            } else {
                origin = "bottom";
                rot = Math.abs(rotation);
            }

            $element.css(createTransformCss(rot, origin, posDiff));

            posDiff += elem.getBoundingClientRect().height - elementBase.height;
            if (origin == "bottom") {
                $element.css(createTransformCss(rot, origin, posDiff));
            }
        }
        return true;
    } else {
        return false;
    }
}

function minDeg() {
    let posDiff = 0;

    let origin;
    if (Math.abs(rotation) < 90 && Math.abs(rotation) >= 0) {

        rotation -= shiftAmount;

        for (var i = 0; i < elements.length; i++) {
            let rot;
            elem = elements[i];
            $element = $(elem);
            if (i % 2 == 0) {
                origin = "top";
                rot = rotation;
            } else {
                origin = "bottom";
                rot = Math.abs(rotation);
            }

            $element.css(createTransformCss(rot, origin, posDiff));

            posDiff += elem.getBoundingClientRect().height - elementBase.height;
            if (origin == "bottom") {
                $element.css(createTransformCss(rot, origin, posDiff));
            }
        }
        return true;
    } else {
        return false;
    }
}


function createTransformCss(rotationDeg, transformOrigin, top, perspective = 200) {
    var shadow = 0;
    shadow = 140 - (Math.abs(rotationDeg) * 2);

    var boxShadow = 'rgba(0, 0, 0, 0.75) 0px 0px 151px -' + shadow + 'px inset';
    if (transformOrigin == "bottom") {
        shadow = 370 - (Math.abs(rotationDeg) * 3);
        boxShadow = 'rgba(0, 0, 0, 0.75) 0px 200px 151px -' + shadow + 'px inset';
    }

    if(Math.abs(rotationDeg) == 0) {
        boxShadow = "none";
    }

    return {
        'top': top + "px",
        'transform-origin': transformOrigin,
        'transform': 'perspective(' + perspective + 'px) rotateX(' + (rotationDeg) + 'deg)',
        'box-shadow': boxShadow
    }
}
.container {
    max-width: 500px;
    margin: 0 auto;
}

.fold-items {
    width: 200px;
    display: flex;
    flex-direction: column;
}

.fold-items section {
    height: 75px;
    position: relative;
    background-color: bisque;
}

.fold-items section.visible {
    display: flex;
    border-bottom: 2px solid black;
}

.fold-items section.visible a {
    margin: auto;
    color: black;
    font-family: 'Roboto', sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    text-decoration: none;
    padding: 5px 10px;
    border: 2px solid black;
    transition: all .3s;
}
.fold-items section.visible a:hover {
    background-color: rgba(0,0,0, 0.3);
    border: 4px solid black;
    color: white;
}

.fold-items section .inner {
    height: 100%;
    width: 100%;
    position: relative;
}

.fold-items section:nth-of-type(5n + 3) {
    background-image: url('https://via.placeholder.com/200x225');
    background-size: cover;
    background-position: top;
}

.fold-items section:nth-of-type(5n + 4) {
    background-image: url('https://via.placeholder.com/200x225');
    background-size: cover;
    background-position: center;
}

.fold-items section:nth-of-type(5n + 5) {
    background-image: url('https://via.placeholder.com/200x225');
    background-size: cover;
    background-position: bottom;
}
<!DOCTYPE html>
<html lang="en">

<head>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="css/style.css">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"
        integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
</head>

<body>
    <div class="container">
        <div class="fold-wrapper">
            <div id="fold-items" class="fold-items">
                <section class="visible">
                    <a id="foldButton" href="#">Fold <span>In</span></a>
                </section>
                <section class="fold">
                </section>
                <section class="fold image">
                </section>
                <section class="fold image">
                </section>
                <section class="fold image">
                </section>
            </div>
        </div>
    </div>

</body>
<script src="js/script.js"></script>

</html>

You can use my snippet by pressing the button or you can use the arrow keys up and down to open or close it in a single step.

UPDATE:

I’ve updated the effect and it now works on scroll like the example.

The new jsfiddle

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP