ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Web] Cross Browsing with Scrolling
    programing/Web 2020. 5. 23. 19:34

    안녕하세요, Einere입니다.

    (광고 차단 기능을 꺼주시면 감사하겠습니다.)


    이번 포스트에서는 스크롤링 기능으로 인한 크로스 브라우징 경험 후기를 공유하고자 합니다.

     

    element.scrollIntoView()

    홈페이지를 개발하다가, 상단의 메뉴를 클릭하면 해당 섹션으로 부드럽게 스크롤되는 기능을 구현하기 위해 scrollIntoView()함수를 사용했습니다.

    평소에 크롬을 쓰는 저는 로컬에서 테스트를 해 보니 잘 작동했습니다. 그런데 어느 날 사파리로 테스트해보니, 부드럽게 스크롤링되지 않고 순식간에 해당 위치로 스크롤이 점프했습니다.

     

    scrollIntoView자체도 실험적인 기능이다

    혹시나 해서 MDN의 scrollIntoView()문서를 보니, 사파리에서는 smooth behavior를 지원하지 않는다고 나와있습니다. (CSS의 scroll-behavior: smooth;도 마찬가지입니다.)

    그렇다면 polyfill로 승부를 볼 수 밖에 없습니다.

     

    browser detection

    우선, 사용자의 브라우저가 어느 브라우저인지 스크립트에서 파악을 해야 합니다. 그 후, 분기문을 통해 적절한 polyfill 로직들을 실행하게끔 하면 됩니다.

    구글링 결과, MDN 및 여러 개발자들이 user agent를 기반으로 browser detection을 하지 말 것을 권장합니다. 여러 가지 이유가 있지만, 제일 중요한 이유는 신뢰할 수 없다는 것입니다. 대신 feature detection을 사용하라고 권장하고 있습니다.

     

    // Opera 8.0+
    var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
    
    // Firefox 1.0+
    var isFirefox = typeof InstallTrigger !== 'undefined';
    
    // Safari 3.0+ "[object HTMLElementConstructor]" 
    var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
    
    // Internet Explorer 6-11
    var isIE = /*@cc_on!@*/false || !!document.documentMode;
    
    // Edge 20+
    var isEdge = !isIE && !!window.StyleMedia;
    
    // Chrome 1 - 79
    var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);
    
    // Edge (based on chromium) detection
    var isEdgeChromium = isChrome && (navigator.userAgent.indexOf("Edg") != -1);
    
    // Blink engine detection
    var isBlink = (isChrome || isOpera) && !!window.CSS;
    
    
    var output = 'Detecting browsers by ducktyping:<hr>';
    output += 'isFirefox: ' + isFirefox + '<br>';
    output += 'isChrome: ' + isChrome + '<br>';
    output += 'isSafari: ' + isSafari + '<br>';
    output += 'isOpera: ' + isOpera + '<br>';
    output += 'isIE: ' + isIE + '<br>';
    output += 'isEdge: ' + isEdge + '<br>';
    output += 'isEdgeChromium: ' + isEdgeChromium + '<br>';
    output += 'isBlink: ' + isBlink + '<br>';
    document.body.innerHTML = output;

    stack overflow에서 찾은 feture detection 코드입니다.

    isSafari는 정상적으로 작동하는 것을 확인했습니다. 그럼 이제 polyfill만 구현하면 되겠군요!

     

    polyfill

    스택오버 플로우에서 찾은 scrollIntoView()의 폴리필 코드입니다.

    function SVS_B(eAmt, where) {
        if(where == "center" || where == "")
            window.scrollBy(0, eAmt / 2);
        if (where == "top")
            window.scrollBy(0, eAmt);
    }
    
    function SmoothVerticalScrolling(e, time, where) {
        var eTop = e.getBoundingClientRect().top;
        var eAmt = eTop / 100;
        var curTime = 0;
        while (curTime <= time) {
            window.setTimeout(SVS_B, curTime, eAmt, where);
            curTime += time / 100;
        }
    }

    잘 되나 볼까요?

     

    움짤이 제대로 보이지 않는다면, 클릭해서 봐주세요

    음.. 뭔가 정상적인 위치도 못 찾고 부드럽지도 않네요.

    또한 setTimeout()을 사용하고 있기 때문에, 성능이 좋지 않습니다.

     

    더 나은 polyfill

    Yuki C.님의 Medium 포스트의 코드의 가독성을 높인 코드입니다. (해당 코드는 위로 스크롤해야 하는 경우는 작동하지 않습니다.)

    export function scroll(element: Element) {
        let start = 0;
        const targetTop = element && element ? element.getBoundingClientRect().top : 0; // relative
        const firstPos = window.pageYOffset || document.documentElement.scrollTop; // absolute
        let currentPos = 0; // absolute
    
        function showAnimation(timestamp: number) {
            if (start === 0) start = timestamp || new Date().getTime();
    
            const elapsed = timestamp - start;
            const progress = elapsed / 500; // animation duration 500ms
    
            currentPos = (targetTop === 0) ? (firstPos - (firstPos * progress)) : (firstPos + (targetTop * progress));
            window.scrollTo(0, currentPos);
    
            if (
                // 최상단으로 가야 하는 경우
                (targetTop === 0 && currentPos <= 0) ||
                // 아래로 스크롤해서 목표 지점을 지나친 경우
                (targetTop > 0 && currentPos >= firstPos + targetTop) ||
                // 위로 스크롤해서 목표 지점을 지나친 경우
                (targetTop < 0 && currentPos <= firstPos + targetTop)
            ) {
                cancelAnimationFrame(start);
                currentPos = 0;
                return;
            }
            window.requestAnimationFrame(showAnimation);
        }
    
        window.requestAnimationFrame(showAnimation);
    }
    

    개인적으로 함수를 값으로 넘겨줘야 하는 경우, 최대한 순수하게 만들기 위해 클로저를 사용하는 것을 별로 좋아하지는 않습니다만.. 추가적인 인자를 받아야 하는 경우 bind()함수를 사용해야 해서 코드가 길어지기 때문에 그냥 클로저를 사용했습니다.

    위 코드가 다른 코드보다 좋은 점으로는 animation duration time을 지정할 수 있다는 것입니다.

    그럼 이제 제대로 작동하는지 볼까요?

     

    움짤이 제대로 보이지 않는다면, 클릭해서 봐주세요

    오 제대로 작동합니다.

     

    후기

    단순하게 사파리에도 적용되게 해야지~하며 시작했다가 구글링하고 이해하고 테스트하느라 몇시간을 잡아먹었습니다.

    구글링하다가 알게된 건데, 크로스 브라우징을 위한 디텍션과 폴리필을 위한 라이브러리도 꽤나 많이 있는 듯 합니다.

    결론은 크로스 브라우징이 참 힘들다는 것입니다.. 😅

     

    'programing > Web' 카테고리의 다른 글

    [Storybook] 스토리북에 대해 알아보자  (2) 2020.06.15
    [Deno] Deno에 대해 알아보자  (3) 2020.05.31
    React vs Vue.js  (0) 2020.05.19
    웹 접근성 커뮤니티  (0) 2020.05.18
    [CSS] scroll-snap  (0) 2020.02.18

    댓글

Designed by black7375.