• Home
  • About
    • Hanna's Blog photo

      Hanna's Blog

      I wanna be a global developer.

    • Learn More
    • Email
    • LinkedIn
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

[Web] iPhone X

18 Aug 2021

Reading time ~2 minutes

Reference by Apple Store iPhone 10

Demo

Code

  • HTML
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>iPhone X</title>
    <style>
        ...
    </style>
</head>
<body>
    <div class="video-wrapper">
        <video muted="" playsinline="" autoplay="" loop="" id="iphone-x" src="https://images.apple.com/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/overview/primary/hero/large_2x.mp4"></video>
    </div>
    <canvas id="cover-canvas"></canvas>
    <script>
        ...
    </script>
</body>
</html>
  • CSS
<style>
    /* Clear default style*/
    html{
        height:100%;
        font-family:sans-serif;
        -webkit-text-size-adjust:100%;
        -ms-text-size-adjust:100%;
        -webkit-tap-highlight-color:rgba(0,0,0,0)
    }
    
    body{
        height:100%;
        -webkit-font-smoothing:antialiased;
        font-smoothing:antialiased;
        -webkit-overflow-scrolling:touch;
        overflow-scrolling:touch
    }
    
    html,body,div,span,applet,object,iframe,figure,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{
        margin:0;
        padding:0;
        border:0
    }
    
    article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{
        display:block
    }
    
    div,article,section,p,ul,li,span,label{
        box-sizing:border-box
    }
    /**********************/

    body{
        background:#000
    }
    
    #cover-canvas{
        position:fixed;
        top:0;
        left:0
    }
    
    .video-wrapper{
        display:flex;
        justify-content:center;
        align-items:center;
        overflow:hidden;
        position:fixed;
        top:0;
        left:0;
        width:100vw;
        height:100vh
    }
    
    #video-studiomeal{
        /* This video scale will be changed */
        transform:scale(1)
    }
</style>
  • JavaScript
<script>
    'use strict';
    (function(){
        var elemCanvas,
            elemVideo,
            elemPhone,
            context,
            windowWidth=0, // window width
            windowHeight=0, // window height
            canvasWidth=0, // canvas width
            canvasHeight=0, // canvas height
            scrollY=0, // current scroll pos
            relativeScrollY=0, // relative scroll pos in each frame
            prevDurations=0, // duration from prev keyframe
            totalScrollHeight=0, // total height for scroll(body height)
            currentKeyframe=0, // current keyframe (0, 1)
            phoneWidth=4000, // iPhone image width
            phoneHeight=4000, // iPhone image height
            resizeHandler,
            scrollHandler,
            render,
            drawCanvas,
            calcAnimationValue,
            calcFinalValue,
            init,
            pixelDuration=0, // scroll height of each keyframe

            // there is 2 keyframes
            // first keyframe: start
            // second keyframe: change X to iPhone
            keyframes=[
                {
                    animationValues:{
                        videoScale:[1,2], // to be bigger
                        triangleMove:[0,200], // X
                        rectangleMove:[0,500]
                    }
                },
                {
                    animationValues:{
                        videoScale:[2,0.5], // to be smaller
                        triangleMove:[200,1000],
                        rectangleMove:[500,500]
                    }
                }
            ],

            // canvas
            elemBody=document.body,
            elemCanvas=document.getElementById('cover-canvas'),
            context=elemCanvas.getContext('2d');
            elemVideo=document.getElementById('iphone-x');

        init=function(){
            windowWidth=window.innerWidth;
            windowHeight=window.innerHeight;

            resizeHandler();
            render();

            // requestAnimationFrame is for smooth rendering
            // because resize and scroll are frequent event.
            window.addEventListener('resize',function(){
                requestAnimationFrame(resizeHandler);
            });
            window.addEventListener('scroll',function(){
                requestAnimationFrame(scrollHandler);
            });

            elemPhone=document.createElement('img');
            elemPhone.src='phone.png';
            elemPhone.addEventListener('load',function(){
                drawCanvas();
            });
        };

        resizeHandler=function(){
            var i;
            windowWidth=window.innerWidth;
            windowHeight=window.innerHeight;
            totalScrollHeight=0;
            // one keyframe duration is half of window height
            pixelDuration=0.5*windowHeight;

            // totalScrollHeight = windowHeight
            for(i=0;i<keyframes.length;i++){
                totalScrollHeight+=pixelDuration;
            }
            totalScrollHeight+=windowHeight;

            elemBody.style.height=totalScrollHeight+'px';
            elemCanvas.width=canvasWidth=windowWidth*2;
            elemCanvas.height=canvasHeight=windowHeight*2;
            elemCanvas.style.width=windowWidth+'px';
            elemCanvas.style.height=windowHeight+'px';
        };

        scrollHandler=function(){
            scrollY=window.pageYOffset; // current scroll pos

            // scroll range valid check
            if(scrollY<0||scrollY>(totalScrollHeight-windowHeight)){
                return;
            }

            // when scroll down 
            if(scrollY>pixelDuration+prevDurations){
                prevDurations+=pixelDuration;
                currentKeyframe++;
            }
            // when scroll up
            else if(scrollY<prevDurations){
                currentKeyframe--;
                prevDurations-=pixelDuration;
            }

            // current keyframe scroll pos
            relativeScrollY=scrollY-prevDurations;
            render();
        };

        render=function(){
            var videoScale,
                triangleMove,
                rectangleMove;
            if(keyframes[currentKeyframe]){
                videoScale=calcAnimationValue(keyframes[currentKeyframe].animationValues.videoScale);
                triangleMove=calcAnimationValue(keyframes[currentKeyframe].animationValues.triangleMove);
                rectangleMove=calcAnimationValue(keyframes[currentKeyframe].animationValues.rectangleMove);
            }
            else{
                return;
            }
            elemVideo.style.transform='scale('+videoScale+')';

            // clear canvas every time before drawing
            context.clearRect(0,0,canvasWidth,canvasHeight);

            if(elemPhone){
                drawCanvas(videoScale,triangleMove,rectangleMove);
            }
        };

        calcAnimationValue=function(values){
            // current keyframe scroll pos / keyframe scroll = ratio
            // values[1]-values[0] is diff value
            // values[0] is init value
            return(relativeScrollY/pixelDuration)*(values[1]-values[0])+values[0];
        };

        drawCanvas=function(videoScale,triangleMove,rectangleMove){
            var videoScale=videoScale||1,
                triangleMove=triangleMove||0,
                rectangleMove=rectangleMove||0;

            context.save();
            context.translate((canvasWidth-phoneWidth*videoScale)*0.5,(canvasHeight-phoneHeight*videoScale)*0.5);
            // phone image scale is changed by video scale
            context.drawImage(elemPhone,0,0,phoneWidth*videoScale,phoneHeight*videoScale);
            context.restore();

            context.fillStyle='black';

            // top triangle
            context.beginPath();
            context.moveTo(canvasWidth*0.5-1500,-triangleMove-1700);
            context.lineTo(canvasWidth*0.5,canvasHeight*0.5-150-triangleMove);
            context.lineTo(canvasWidth*0.5+1500,-triangleMove-1700);
            context.lineTo(canvasWidth*0.5-1500,-triangleMove-1700);
            context.fill();
            context.closePath();

            // bottom triangle
            context.beginPath();
            context.moveTo(canvasWidth*0.5-1500,canvasHeight+triangleMove+1700);
            context.lineTo(canvasWidth*0.5,canvasHeight*0.5+150+triangleMove);
            context.lineTo(canvasWidth*0.5+1500,canvasHeight+triangleMove+1700);
            context.lineTo(canvasWidth*0.5-1500,canvasHeight+triangleMove+1700);
            context.fill();
            context.closePath();

            // left triangle
            context.beginPath();
            context.moveTo(canvasWidth*0.5-1700-triangleMove,-1700);
            context.lineTo(canvasWidth*0.5-130-triangleMove,canvasHeight*0.5);
            context.lineTo(canvasWidth*0.5-1700-triangleMove,canvasHeight+1700);
            context.lineTo(canvasWidth*0.5-1700-triangleMove,-1700);
            context.fill();
            context.closePath();

            // right triangle
            context.beginPath();
            context.moveTo(canvasWidth*0.5+1700+triangleMove,-1700);
            context.lineTo(canvasWidth*0.5+130+triangleMove,canvasHeight*0.5);
            context.lineTo(canvasWidth*0.5+1700+triangleMove,canvasHeight+1700);
            context.lineTo(canvasWidth*0.5+1700+triangleMove,-1700);
            context.fill();
            context.closePath();

            // Box top, bottom
            context.fillRect(0,canvasHeight*0.5-2600-rectangleMove,canvasWidth,2000);
            context.fillRect(0,canvasHeight*0.5+600+rectangleMove,canvasWidth,2000);
        };

        init();
    })();
</script>

Download



HTMLCSSJavaScriptWeb Share Tweet +1