requestAnimationFrame() ํด๋ฆฌํ•„

requestAnimationFrame() ๋ž€?

requestAnimationFrame (์ดํ•˜ rAF)์€ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ˆ˜ํ–‰ํ•˜๊ธฐ๋ฅผ ์›ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”๋‹ˆ์…˜์„ ์•Œ๋ฆฌ๊ณ  ๋‹ค์Œ ๋ฆฌํŽ˜์ธํŠธ๊ฐ€ ์ง„ํ–‰๋˜๊ธฐ ์ „์— ํ•ด๋‹น ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ๋ฆฌํŽ˜์ธํŠธ ์ด์ „์— ์‹คํ–‰ํ•  ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

let start = null;
const element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

const step = timestamp => {
  if (!start) start = timestamp;
  const progress = timestamp - start;
  element.style.left = Math.min(progress / 10, 200) + 'px';
  if (progress < 2000) {
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);

์ด ํ•จ์ˆ˜๋Š” ์œ„ ์˜ˆ์‹œ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์š”์†Œ๋ฅผ ์›€์ง์ด๊ฑฐ๋‚˜, ๋น„๋™๊ธฐ์ ์ธ ๋ฐ˜๋ณต์ž‘์—…์„ ํ• ๋•Œ์— ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ด๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํด๋ฆฌํ•„๋ง์ด ํ•„์š”ํ•˜๋‹ค. rAF์˜ ๋™์ž‘์„ ๋Œ€ํ•ด ์กฐ๊ธˆ ๋” ํ™•์‹คํ•˜๊ฒŒ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์ง์ ‘ ๋งŒ๋“ค์–ด ๋ณด์•˜๋‹ค.

timestamp

window.requestAnimationFrame()์˜ ์ธ์ž๋กœ๋Š” ๋‹ค์Œ ๋ฆฌํŽ˜์ธํŠธ๋ฅผ ์œ„ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์—…๋ฐ์ดํŠธํ• ๋•Œ ํ˜ธ์ถœํ•  ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ๋ฐ›๋Š”๋‹ค. ์ด ์ฝœ ๋ฐฑํ•จ์ˆ˜์—๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ• ๋•Œ ์‹œ์ ์„ ๋‚˜ํƒ€๋‚ด๋Š” performance.now() ์˜ ๋ฐ˜ํ™˜๊ฐ’๊ณผ ์œ ์‚ฌํ•œ DOMHighResTimeStamp๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.

(function(global){
  var nowOffest = Date.now(); // ๋ธŒ๋ผ์šฐ์ €์˜ ์‹คํ–‰์‹œ์ 
  var timeStamp = function(){
    // performance๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
    if(global.performance && 
       typeof global.performance.now && 
       typeof global.performance.now === 'function'
    ){
      return global.performance.now();
    }
    // fallback
    return Date.now() - nowOffest;
  }

})(window);

prefix

๋ชจ๋˜ ๋ธŒ๋ผ์šฐ์ €๋“ค์€ ๋Œ€๋ถ€๋ถ„ raf๋ฅผ ์ง€์›ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ธŒ๋ผ์šฐ์ €๋ณ„๋กœ ํ”„๋ฆฌํ”ฝ์Šค๊ฐ€ ๋ถ™์–ด์žˆ๋Š” ํ•จ์ˆ˜๋“ค์„ ๊ฐ์‹ธ์ฃผ๋Š”๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค.

(function(global){
  // timeStamp...

  var prefix='';
  if('mozRequestAnimationFrame' in global){
    prefix = 'moz';
  }else if ('webkitRequestAnimationFrame' in global){
    prefix = 'webkit';
  }

  if(!!prefix){
      global.requestAnimationFrame = function(callback){
        return global[prefix + 'RequestAnimationFrame'](function(){
          callback(timeStamp());
        });
    }

    global.cancelAnimationFrame = global[prefix + 'CancelAnimationFrame'];
  }
})(window);

raf๋ฅผ ์ง€์›ํ•˜๊ณ  ์žˆ์ง€ ์•Š์€ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €๋“ค์€ setTimeout()ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ํด๋ฆฌํ•„๋งํ•œ๋‹ค.

if(!!prefix){
  // raf๋ฅผ ์ง€์›ํ•˜๋Š”๊ฒฝ์šฐ
}else{
  var lastTime = Date.now(); // ํด๋กœ์ €
  global.requestAnimationFrame = function(callback){
    var currentTime = Date.now();
    var delay = lastTime - currentTime + 16;
    
    if(delay < 0){
      delay = 0;
    }

    lastTime = currentTime;

    return setTimeout(function(){
      lastTime = Date.now();
      callback(timeStamp());
    }, delay);
  };

  global.cancelAnimation = clearTimout;
}

๊ฒฐ๊ณผ

(function(global){
  // timeStamp...
  var nowOffest = Date.now(); // ๋ธŒ๋ผ์šฐ์ €์˜ ์‹คํ–‰์‹œ์ 
  var timeStamp = function(){
    // performance๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
    if(global.performance && 
       typeof global.performance.now && 
       typeof global.performance.now === 'function'
    ){
      return global.performance.now();
    }
    // fallback
    return Date.now() - nowOffest;
  }

  var prefix='';
  if('mozRequestAnimationFrame' in global){
    prefix = 'moz';
  }else if ('webkitRequestAnimationFrame' in global){
    prefix = 'webkit';
  }

  if(!!prefix){
      global.requestAnimationFrame = function(callback){
        return global[prefix + 'RequestAnimationFrame'](function(){
          callback(timeStamp());
        });
    }

    global.cancelAnimationFrame = global[prefix + 'CancelAnimationFrame'];
  }else{
    var lastTime = Date.now(); // ํด๋กœ์ €
    global.requestAnimationFrame = function(callback){
      var currentTime = Date.now(),
          delay = lastTime - currentTime + 16;
      if(delay < 0){
        delay = 0;
      }
      lastTime = currentTime;

      return setTimeout(function(){
        lastTime = Date.now();
        callback(timeStamp());
      }, delay);
    };

    global.cancelAnimation = clearTimout;
  }
})(window);

์ฐธ๊ณ