div.stage: ul.flyer
    li.flyer__page.flyer__page--6
    li.flyer__page.flyer__page--5
    li.flyer__page.flyer__page--7
    li.flyer__page.flyer__page--4
    li.flyer__page.flyer__page--8
    li.flyer__page.flyer__page--3
    li.flyer__page.flyer__page--1
    li.flyer__page.flyer__page--2
View Compiled
body
    margin: 0
    background: #3c6ea5
    overflow: hidden
ul
    margin: 0
    padding: 0
li
    list-style: none

.stage
    width: 480px
    margin: 100px auto 0 auto
    perspective: 800px
    perspective-origin: 50% 180px

@keyframes rotate
    to
        transform: rotateY(360deg)

.flyer
    position: relative
    transform-style: preserve-3d
    transform-origin: 50%
    animation: rotate 4s alternate

    &__page
        $width: 198px
        $depth: $width / 2
        $deg: 45deg

        width: $width
        height: round($width * 2.1155)
        position: absolute
        backface-visibility: hidden
        background: url(https://asistapl.github.io/assets/exporia/mk/mk.jpg) no-repeat
        background-size: 400%
        opacity: .95

        &--6
            background-position: left bottom
            transform: rotateY(-$deg) translateZ($depth)
        &--5
            background-position: right top
            transform: rotateY($deg * 3) translateZ(-$depth)
        &--7
            background-position: -$width bottom
            transform: rotateY($deg) translateZ($depth)
        &--4
            background-position: -$width * 2 top
            transform: rotateY(-$deg * 3) translateZ(-$depth)
        &--8
            background-position: -$width * 2 bottom
            transform: rotateY(-$deg) translateZ(-$depth) translateX(100%)
        &--3
            background-position: -$width top
            transform: rotateY($deg * 3) translateZ($depth) translateX(-100%)
        &--1
            background-position: right bottom
            transform: rotateY($deg) translateZ($depth * 3) translateX(100%)
        &--2
            background-position: left top
            transform: rotateY(-$deg * 3) translateZ(-$depth * 3) translateX(-100%)

.js-disable-animation
    animation: none
View Compiled
let $stage = $('.stage')
let $flyer = $('.flyer')

const BASEPERSPECTIVE = 180
const BASEEASING = 10
const MAXDEGREE = 360

let rotation = { current: 0, final: 0 }
let perspective = { current: BASEPERSPECTIVE, final: BASEPERSPECTIVE }
let zoom = { current: 1, final: 1 }
let easing = BASEEASING

let ease = val => val.current += (val.final - val.current ) / easing
let round = val => Math.round(val * 1000) / 1000
let finished = val => round(val.current) === round(val.final)

let animation = {
    _isPlaying: false,
    play() {
        !this._isPlaying && this._animate()
    },
    _animate() {
        this._isPlaying = true

        ease(rotation)
        ease(perspective)
        ease(zoom)

        $flyer.style.transform = `rotateY(-${ rotation.current }deg)`
        $stage.style.perspectiveOrigin = `center ${ perspective.current }px`
        $stage.style.transform = `scale(${ zoom.current })`

        if (finished(rotation)
        &&  finished(perspective)
        &&  finished(zoom)) {
            this._isPlaying = false
            return
        }

        requestAnimationFrame(this._animate.bind(this))
    }
}

document.on('mousemove', e => {
    rotation.final = MAXDEGREE * e.clientX / window.innerWidth
    perspective.final = (e.clientY - window.innerHeight / 2)
                        * 2 + BASEPERSPECTIVE

    animation.play()
})

document.on('mouseover', () => {
    $flyer.classList.add('js-disable-animation')
    easing = BASEEASING

    animation.play()
})

document.on('mouseleave', () => {
    rotation.final = 0
    perspective.final = BASEPERSPECTIVE
    zoom.final = 1
    easing = BASEEASING * 5

    animation.play()
})

window.on('mousewheel, DOMMouseScroll', e => {
    let wheelDistance = e.detail ? -e.detail / 3 : e.wheelDelta / 120

    zoom.final += wheelDistance / 10
    zoom.final = Math.min(Math.max(zoom.final, .25), 2)

    animation.play()
})

document.on('click', () => {
    zoom.final = 1

    animation.play()
})
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://codepen.io/exporia/pen/epaLVO.js