본문 바로가기
공부하기

자바스크립트 성능 최적화

by 날아라못난 2024. 2. 28.
728x90
반응형

pixabay

개요

웹 사이트에서 JavaScript를 사용하는 방법을 고려하고 이로 인해 발생할 수 있는 성능 문제를 완화하는 방법에 대해 생각하는 것은 매우 중요합니다. 이미지와 비디오는 평균 웹 사이트의 바이트당 다운로드 바이트 중 70% 이상을 차지하지만 JavaScript는 성능에 부정적인 영향을 미칠 가능성이 더 큽니다. 즉, 다운로드 시간, 렌더링 성능, CPU 및 배터리 사용량에 큰 영향을 미칠 수 있습니다. 이 문서에서는 JavaScript를 최적화하여 웹 사이트의 성능을 향상시키는 팁과 기술을 소개합니다.

 

최적화하거나 최적화하지 않으려면

코드 최적화를 시작하기 전에 대답해야 할 첫 번째 질문은 "최적화하려면 무엇이 필요합니까?"입니다. 아래에 논의된 팁과 기술 중 일부는 모든 웹 프로젝트에 도움이 되는 좋은 사례이지만, 일부는 특정 상황에서만 필요합니다. 이러한 모든 기술을 모든 곳에 적용하려고 하는 것은 아마도 불필요할 것이며 시간 낭비일 수도 있습니다. 각 프로젝트에서 실제로 어떤 성능 최적화가 필요한지 파악해야 합니다.

이를 위해서는 사이트의 성능을 측정 해야 합니다. 이전 링크에서 볼 수 있듯이 성능을 측정하는 방법에는 여러 가지가 있으며 그 중 일부에는 정교한 성능 API가 포함됩니다 . 그러나 시작하는 가장 좋은 방법은 내장된 브라우저 네트워크 및 성능 도구와 같은 도구를 사용하는 방법을 배우고 페이지 로드의 어떤 부분이 시간이 오래 걸리고 최적화가 필요한지 확인하는 것입니다.

 

JavaScript 다운로드 최적화

사용할 수 있는 가장 성능이 뛰어나고 차단이 가장 적은 JavaScript는 전혀 사용하지 않는 JavaScript입니다. 가능한 한 적은 양의 JavaScript를 사용해야 합니다. 명심해야 할 몇 가지 팁:

  • 프레임워크가 항상 필요한 것은 아닙니다 . JavaScript 프레임워크 사용에 익숙할 수도 있습니다 . 이 프레임워크 사용에 대한 경험이 있고 자신감이 있고 이 프레임워크가 제공하는 모든 도구가 마음에 든다면 이 프레임워크가 대부분의 프로젝트를 구축하는 데 적합한 도구일 수 있습니다. 그러나 프레임워크에는 JavaScript가 많이 사용됩니다. JavaScript 요구 사항이 거의 없는 매우 정적인 환경을 만드는 경우에는 해당 프레임워크가 필요하지 않을 것입니다. 몇 줄의 표준 JavaScript를 사용하여 필요한 것을 구현할 수 있습니다.
  • 더 간단한 솔루션 고려 : 구현해야 할 화려하고 흥미로운 솔루션이 있을 수 있지만 사용자가 이를 좋아할지 여부를 고려하십시오. 그들은 더 간단한 것을 선호할까요?
  • 사용하지 않는 코드 제거: 당연하게 들릴 수도 있지만 개발 프로세스 중에 추가된 사용하지 않는 기능을 정리하는 것을 잊어버린 개발자가 얼마나 많은지 놀랍습니다. 무엇이 추가되고 제거되는지 주의 깊게 숙고해야 합니다. 모든 스크립트는 사용 여부에 관계없이 구문 분석됩니다. 따라서 다운로드 속도를 높이는 빠른 방법은 사용되지 않는 기능을 제거하는 것입니다. 프레임워크에서 사용할 수 있는 기능 중 극히 일부만 사용하는 경우가 많다는 점도 고려하세요. 필요한 부분만 포함된 프레임워크의 사용자 정의 빌드를 생성할 수 있습니까?
  • 내장된 브라우저 기능을 고려하십시오 . JavaScript를 통해 자체 기능을 만드는 대신 브라우저에 이미 있는 기능을 사용할 수 있습니다. 예를 들어:

또한 JavaScript를 중요한 부분과 중요하지 않은 부분을 나타내는 여러 파일로 분할해야 합니다. JavaScript 모듈을 사용하면 별도의 외부 JavaScript 파일을 사용하는 것보다 더 효율적으로 이 작업을 수행할 수 있습니다.

그런 다음 이러한 작은 파일을 최적화할 수 있습니다. 코드 경량화는 파일의 문자 수를 줄여서 JavaScript의 바이트 수나 무게를 줄입니다. Gzipping은 파일을 더욱 압축하므로 코드를 축소하지 않더라도 사용해야 합니다. Brotli 는 Gzip과 유사하지만 일반적으로 Gzip 압축보다 성능이 뛰어납니다.

코드를 수동으로 분할하고 최적화할 수 있지만 Webpack 과 같은 모듈 번들러가 이 작업을 더 잘 수행하는 경우가 많습니다.

코드 경량화(Minification) 는 브라우저에서 리소스를 처리하는 방식에 영향을 주지 않고 불필요하거나 중복되는 데이터를 제거하는 프로세스입니다.
코드 경량화에는 코드 주석, 공백, 사용되지 않는 코드 제거는 물론 변수 및 함수 이름 줄이는 것도 포함될 수 있습니다. 코드 경량화는 파일 크기를 줄여 웹 성능을 향상시키는 데 사용됩니다. 코드 경량화는 일반적으로 빌드 시 발생하는 자동화된 단계입니다.
코드 경량화로 인해 코드를 사람이 읽기가 어려워지므로, 개발자 도구에는 코드에 공백을 다시 추가하는 방식으로 좀 더 읽기 쉽게 만들어주는 '코드 정리(prettification)' 기능이 있습니다.

구문 분석 및 실행 처리

이 섹션에 포함된 팁을 살펴보기 전에 브라우저 페이지 렌더링 프로세스에서 JavaScript가 처리되는 위치 에 대해 이야기하는 것이 중요합니다. 웹페이지가 로드되면:

HTML은 일반적으로 페이지에 나타나는 순서대로 먼저 구문 분석됩니다.
CSS가 발견될 때마다 페이지에 적용해야 하는 스타일을 이해하기 위해 구문 분석됩니다. 이 시간 동안 이미지, 웹 글꼴 등 연결된 자산을 가져오기 시작합니다.
JavaScript가 발견될 때마다 브라우저는 페이지에 대해 JavaScript를 구문 분석하고 평가하고 실행합니다.
조금 후에 브라우저는 적용된 CSS를 고려하여 각 HTML 요소의 스타일을 지정하는 방법을 결정합니다.
그런 다음 스타일이 지정된 결과가 화면에 그려집니다.

참고: 이것은 무슨 일이 일어나는지에 대한 매우 단순화된 설명이지만 아이디어를 제공합니다.

 

여기서 핵심 단계는 3단계입니다. 기본적으로 JavaScript 구문 분석 및 실행은 렌더링 차단입니다. 이는 스크립트가 처리될 때까지 JavaScript가 발견된 후 나타나는 HTML의 구문 분석을 브라우저가 차단한다는 의미입니다. 결과적으로 스타일링과 페인팅도 차단됩니다. 이는 다운로드하는 내용뿐만 아니라 해당 코드가 실행되는 시기와 방법에 대해서도 신중하게 생각해야 함을 의미합니다.

다음 몇 섹션에서는 JavaScript의 구문 분석 및 실행을 최적화하는 데 유용한 기술을 제공합니다.

 

가능한 한 빨리 중요 자산 로드

스크립트가 정말 중요하고 충분히 빠르게 로드되지 않아 성능에 영향을 미칠까 걱정된다면 <head>문서 내부에 스크립트를 로드할 수 있습니다.

<head>
  ...
  <script src="main.js"></script>
  ...
</head>

 

이것은 잘 작동하지만 렌더링을 차단합니다. 더 나은 전략은 rel="preload"중요한 JavaScript용 프리로더를 만드는 것입니다.

<head>
  ...
  <!-- Preload a JavaScript file -->
  <link rel="preload" href="important-js.js" as="script" />
  <!-- Preload a JavaScript module -->
  <link rel="modulepreload" href="important-module.js" />
  ...
</head>

 

사전 로드는 <link>렌더링을 차단하지 않고 가능한 한 빨리 JavaScript를 가져옵니다. 그런 다음 페이지의 원하는 곳 어디에서나 사용할 수 있습니다.

<!-- Include this wherever makes sense -->
<script src="important-js.js"></script>

 

또는 JavaScript 모듈의 경우 스크립트 내부에서:

import { function } from "important-module.js";
참고:
 미리 로드한다고 해서 스크립트를 포함할 때까지 스크립트가 로드된다는 보장은 없지만 더 빨리 다운로드되기 시작한다는 의미입니다. 렌더링 차단 시간은 완전히 제거되지 않더라도 계속 단축됩니다.

 

 

중요하지 않은 JavaScript 실행 연기

반면에, 중요하지 않은 JavaScript의 구문 분석 및 실행은 필요할 때 나중에 연기하는 것을 목표로 해야 합니다. 불필요하게 렌더링되는 모든 선행 블록을 로드합니다.

우선 요소 async에 속성을 추가할 수 있습니다 <script>.

<head>
  ...
  <script async src="main.js"></script>
  ...
</head>

 

이렇게 하면 DOM 구문 분석과 동시에 스크립트를 가져오므로 동시에 준비되고 렌더링이 차단되지 않습니다.

참고:defer 문서가 구문 분석된 후 이벤트가 발생하기 전에 스크립트가 실행되도록 하는 또 다른 속성이 있습니다 DOMContentLoaded. 이는 와 비슷한 효과를 갖습니다 async.

 

필요할 때 이벤트가 발생할 때까지 JavaScript를 전혀 로드하지 않을 수도 있습니다. 이는 DOM 스크립팅을 통해 수행할 수 있습니다. 예를 들면 다음과 같습니다.

const scriptElem = document.createElement("script");
scriptElem.src = "index.js";
scriptElem.addEventListener("load", () => {
  // Run a function contained within index.js once it has definitely loaded
  init();
});
document.head.append(scriptElem);

 

JavaScript 모듈은 다음 함수를 사용하여 동적으로 로드할 수 있습니다 import().

import("./modules/myModule.js").then((module) => {
  // Do something with the module
});

 

 

긴 작업 나누기

브라우저가 JavaScript를 실행할 때 가져오기 요청 만들기, 이벤트 핸들러를 통한 사용자 상호 작용 및 입력 유도, JavaScript 기반 애니메이션 실행 등과 같이 순차적으로 실행되는 작업으로 스크립트를 구성합니다.

Web Workers 에서 실행되는 JavaScript를 포함한 예외를 제외하고 대부분의 작업은 메인 스레드에서 발생합니다 . 메인 스레드는 한 번에 하나의 작업만 실행할 수 있습니다.

단일 작업을 실행하는 데 50ms 이상이 걸리는 경우 긴 작업으로 분류됩니다. 사용자가 페이지와 상호 작용을 시도하거나 긴 작업이 실행되는 동안 중요한 UI 업데이트가 요청되면 사용자 경험이 영향을 받습니다. 예상되는 응답이나 시각적 업데이트가 지연되어 UI가 느리거나 응답하지 않는 것처럼 보입니다.

이 문제를 완화하려면 긴 작업을 더 작은 작업으로 나누어야 합니다. 이를 통해 브라우저는 중요한 사용자 상호 작용 처리 또는 UI 렌더링 업데이트를 수행할 수 있는 더 많은 기회를 얻을 수 있습니다. 브라우저는 긴 작업 전이나 후에만 수행하는 것이 아니라 잠재적으로 각각의 작은 작업 간에 이러한 작업을 수행할 수 있습니다. JavaScript에서는 코드를 별도의 함수로 나누어 이를 수행할 수 있습니다. 이는 더 쉬운 유지 관리, 디버깅 및 테스트 작성과 같은 여러 가지 다른 이유로도 의미가 있습니다.

 

예를 들어:

function main() {
  a();
  b();
  c();
  d();
  e();
}

 

그러나 이러한 종류의 구조는 메인 스레드 차단에 도움이 되지 않습니다. 다섯 가지 기능이 모두 하나의 기본 기능 내에서 실행되므로 브라우저는 이 기능을 모두 하나의 긴 작업으로 실행합니다.

이를 처리하기 위해 우리는 주기적으로 "yield" 함수를 실행하여 코드가 메인 스레드에 양보 되도록 하는 경향이 있습니다 . 이는 코드가 여러 작업으로 분할되어 실행 사이에 브라우저가 UI 업데이트와 같은 우선순위가 높은 작업을 처리할 수 있는 기회를 제공한다는 것을 의미합니다. 이 함수의 일반적인 패턴은 setTimeout()실행을 별도의 작업으로 연기하는 데 사용됩니다.

function yield() {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

 

이는 각 작업이 실행된 후 기본 스레드에 양보하기 위해 작업 실행기 패턴 내에서 사용할 수 있습니다.

async function main() {
  // Create an array of functions to run
  const tasks = [a, b, c, d, e];

  // Loop over the tasks
  while (tasks.length > 0) {
    // Shift the first task off the tasks array
    const task = tasks.shift();

    // Run the task
    task();

    // Yield to the main thread
    await yield();
  }
}

 

이를 더욱 개선하기 위해 사용자가 페이지와 상호작용을 시도할 때만 함수를 navigator.scheduling.isInputPending()실행하는 데 사용할 수 있습니다 .yield()

async function main() {
  // Create an array of functions to run
  const tasks = [a, b, c, d, e];

  while (tasks.length > 0) {
    // Yield to a pending user input
    if (navigator.scheduling.isInputPending()) {
      await yield();
    } else {
      // Shift the first task off the tasks array
      const task = tasks.shift();

      // Run the task
      task();
    }
  }
}

 

이를 통해 사용자가 페이지와 적극적으로 상호 작용할 때 기본 스레드를 차단하는 것을 방지하여 잠재적으로 보다 원활한 사용자 경험을 제공할 수 있습니다. 그러나 필요한 경우에만 양보함으로써 처리할 사용자 입력이 없을 때 현재 작업을 계속 실행할 수 있습니다. 이는 또한 현재 작업 이후에 예약된 다른 중요하지 않은 브라우저 시작 작업 뒤의 대기열 뒤에 작업이 배치되는 것을 방지합니다.

 

JavaScript 애니메이션 처리

애니메이션은 인식된 성능을 향상시켜 인터페이스가 더 빠르게 느껴지도록 하고 사용자가 페이지 로드를 기다릴 때(예: 스피너 로드) 진행 중인 것처럼 느끼게 합니다. 그러나 더 큰 애니메이션과 더 많은 수의 애니메이션을 처리하려면 당연히 더 많은 처리 능력이 필요하므로 성능이 저하될 수 있습니다.

가장 확실한 애니메이션 조언은 애니메이션을 적게 사용하라는 것입니다. 불필요한 애니메이션을 잘라내거나 사용자가 저전력 장치나 모바일을 사용하는 경우 애니메이션을 끄도록 설정할 수 있는 기본 설정을 제공하는 것이 좋습니다. 배터리 전원이 제한된 장치.

필수 DOM 애니메이션의 경우 가능한 경우 JavaScript 애니메이션보다는 CSS 애니메이션을 사용하는 것이 좋습니다 ( Web Animations API는 JavaScript를 사용하여 CSS 애니메이션에 직접 연결하는 방법을 제공합니다). JavaScript를 사용하여 인라인 스타일을 조작하는 것보다 브라우저를 사용하여 DOM 애니메이션을 직접 수행하는 것이 훨씬 빠르고 효율적입니다. CSS 성능 최적화 > 애니메이션 처리를 참조하세요 .

HTML 애니메이션과 같이 JavaScript에서 처리할 수 없는 애니메이션의 경우 와 같은 이전 옵션보다는 <canvas>사용하는 것이 좋습니다 . 이 방법은 원활한 사용자 경험을 위해 애니메이션 프레임을 효율적이고 일관되게 처리하도록 특별히 설계되었습니다. 기본 패턴은 다음과 같습니다.Window.requestAnimationFrame()setInterval()requestAnimationFrame()

function loop() {
  // Clear the canvas before drawing the next frame of the animation
  ctx.fillStyle = "rgb(0 0 0 / 25%)";
  ctx.fillRect(0, 0, width, height);

  // Draw objects on the canvas and update their positioning data
  // ready for the next frame
  for (const ball of balls) {
    ball.draw();
    ball.update();
  }

  // Call requestAnimationFrame to run the loop() function again
  // at the right time to keep the animation smooth
  requestAnimationFrame(loop);
}

// Call the loop() function once to set the animation running
loop();

그래픽 그리기 > 애니메이션 에서 캔버스 애니메이션에 대한 유용한 소개를 찾을 수 있고, 개체 만들기 실습 에서 더 자세한 예제를 찾을 수 있습니다 . 캔버스 튜토리얼 에서 전체 캔버스 튜토리얼 세트를 찾을 수도 있습니다 .

 

이벤트 성능 최적화

특히 이벤트를 지속적으로 실행하는 경우 브라우저에서 이벤트를 추적하고 처리하는 데 비용이 많이 들 수 있습니다. 예를 들어 이벤트를 사용하여 마우스 위치를 추적하여 mousemove마우스가 여전히 페이지의 특정 영역 내에 있는지 확인할 수 있습니다.

 

function handleMouseMove() {
  // Do stuff while mouse pointer is inside elem
}

elem.addEventListener("mousemove", handleMouseMove);

<canvas>페이지에서 게임을 실행 중일 수 있습니다 . 마우스가 캔버스 안에 있는 동안 마우스 움직임과 커서 위치를 지속적으로 확인하고 점수, 시간, 모든 스프라이트 위치, 충돌 감지 정보 등을 포함한 게임 상태를 업데이트해야 합니다. 결국, 더 이상 모든 작업을 수행할 필요가 없으며 실제로 해당 이벤트를 계속 수신하는 것은 처리 능력을 낭비하게 됩니다.

따라서 더 이상 필요하지 않은 이벤트 리스너를 제거하는 것이 좋습니다. 이는 다음을 사용하여 수행할 수 있습니다 removeEventListener().

elem.removeEventListener("mousemove", handleMouseMove);

 

또 다른 팁은 가능하면 이벤트 위임을 사용하는 것입니다. 사용자가 수많은 하위 요소 중 하나와 상호 작용하는 것에 대한 응답으로 실행할 코드가 있는 경우 해당 상위 요소에 이벤트 리스너를 설정할 수 있습니다. 하위 요소에서 발생한 이벤트는 해당 상위 요소까지 버블링되므로 각 하위 요소에 대해 이벤트 리스너를 개별적으로 설정할 필요가 없습니다. 추적해야 할 이벤트 리스너가 적다는 것은 성능이 더 좋다는 것을 의미합니다.

자세한 내용과 유용한 예는 이벤트 위임을 참조하세요 .

 

 

보다 효율적인 코드 작성을 위한 팁

코드를 보다 효율적으로 실행하는 데 도움이 되는 몇 가지 일반적인 모범 사례가 있습니다.

DOM 조작 감소 : DOM에 액세스하고 업데이트하는 것은 계산 비용이 많이 들기 때문에 특히 지속적인 DOM 애니메이션을 수행할 때 JavaScript가 수행하는 양을 최소화해야 합니다( 위의 JavaScript 애니메이션 처리 참조 ).
일괄 DOM 변경 : 필수 DOM 변경의 경우 개별 변경이 발생할 때마다 실행하는 대신 함께 수행되는 그룹으로 일괄 처리해야 합니다. 이는 브라우저가 실제로 수행하는 작업량을 줄일 수 있지만 인지된 성능도 향상시킬 수 있습니다. 지속적으로 작은 업데이트를 하는 것보다 한 번에 여러 업데이트를 수행하여 UI를 더 매끄럽게 보이게 할 수 있습니다. 여기서 유용한 팁은 페이지에 추가할 HTML의 양이 많은 경우 DocumentFragment각 항목을 개별적으로 추가하는 대신 전체 조각을 먼저 빌드한 다음(일반적으로 a 내부) DOM에 한 번에 추가하는 것입니다.
HTML을 단순화하세요 . DOM 트리가 단순할수록 JavaScript로 더 빠르게 액세스하고 조작할 수 있습니다. UI에 필요한 것이 무엇인지 신중하게 생각하고 불필요한 불필요한 부분을 제거하세요.
루프 코드 양 줄이기 : 루프는 비용이 많이 들기 때문에 가능한 한 코드에서 루프 사용량을 줄이십시오. 루프가 불가피한 경우:
필요하지 않은 경우 전체 루프를 실행하지 말고 적절하게 break또는 continue문을 사용하세요. 예를 들어, 배열에서 특정 이름을 검색하는 경우 이름이 발견되면 루프에서 벗어나야 합니다. 추가 루프 반복을 실행할 필요가 없습니다.

function processGroup(array) {
  const toFind = "Bob";
  for (let i = 0; i < array.length - 1; i++) {
    if (array[i] === toFind) {
      processMatchingArray(array);
      break;
    }
  }
}

 

루프 외부에서 한 번만 필요한 작업을 수행합니다. 이는 다소 당연하게 들릴 수도 있지만 간과하기 쉽습니다. 어떤 방식으로든 처리할 데이터가 포함된 JSON 개체를 가져오는 다음 코드 조각을 살펴보세요. 이 경우 fetch()루프를 반복할 때마다 작업이 수행되므로 컴퓨팅 성능이 낭비됩니다. 라인 3과 4는 루프 외부로 이동할 수 있으므로 네트워크 가져오기는 한 번만 수행됩니다.

async function returnResults(number) {
  for (let i = 0; i < number; i++) {
    const response = await fetch(`/results?number=${number}`);
    const results = await response.json();
    processResult(results[i]);
  }
}

 

메인 스레드에서 계산 실행 : 앞서 우리는 JavaScript가 일반적으로 메인 스레드에서 작업을 실행하는 방법과 작업이 메인 스레드를 얼마나 오랫동안 차단하여 잠재적으로 UI 성능을 저하시킬 수 있는지에 대해 이야기했습니다. 또한 긴 작업을 더 작은 작업으로 나누어 이 문제를 완화하는 방법도 보여주었습니다. 이러한 문제를 처리하는 또 다른 방법은 작업을 메인 스레드 밖으로 완전히 옮기는 것입니다. 이를 달성하는 몇 가지 방법이 있습니다:
비동기 코드 사용: 비동기 JavaScript는 기본적으로 기본 스레드를 차단하지 않는 JavaScript입니다. 비동기 API는 네트워크에서 리소스 가져오기, 로컬 파일 시스템의 파일 액세스, 사용자 웹캠에 대한 스트림 열기 등의 작업을 처리하는 경향이 있습니다. 이러한 작업은 오랜 시간이 걸릴 수 있으므로 작업이 완료될 때까지 기다리는 동안 메인 스레드를 차단하는 것은 좋지 않습니다. 대신, 브라우저는 해당 함수를 실행하고, 메인 스레드가 후속 코드를 계속 실행하도록 하며, 해당 함수는 향후 특정 시점에 사용 가능해지면 결과를 반환합니다 . 최신 비동기 API는 Promise비동기 작업을 처리하기 위해 설계된 JavaScript 언어 기능인 기반입니다. 비동기식으로 실행하면 이점을 얻을 수 있는 기능이 있는 경우 Promise 기반 함수를 직접 작성할 수 있습니다 .
웹 작업자에서 계산 실행: 웹 작업자는 별도의 스레드를 열어서 JavaScript 덩어리를 실행할 수 있도록 하는 메커니즘이므로 기본 스레드가 차단되지 않습니다. 작업자에는 몇 가지 주요 제한 사항이 있는데, 가장 큰 것은 작업자 내에서 DOM 스크립팅을 수행할 수 없다는 것입니다. 대부분의 다른 작업을 수행할 수 있으며 작업자는 기본 스레드와 메시지를 주고받을 수 있습니다. 작업자의 주요 사용 사례는 수행할 계산이 많고 기본 스레드를 차단하지 않으려는 경우입니다. 작업자에서 해당 계산을 수행하고 결과를 기다린 후 준비가 되면 기본 스레드로 다시 보냅니다.
WebGPU 사용 : WebGPU 는 웹 개발자가 기본 시스템의 GPU(그래픽 처리 장치)를 사용하여 고성능 계산을 수행하고 브라우저에서 렌더링할 수 있는 복잡한 이미지를 그릴 수 있도록 하는 브라우저 API입니다. 상당히 복잡하지만 웹 작업자보다 훨씬 더 나은 성능 이점을 제공할 수 있습니다.

 

출처:https://developer.mozilla.org/en-US/docs/Learn/Performance/JavaScript

728x90
반응형

'공부하기' 카테고리의 다른 글

스크롤 위치 알아내기  (3) 2024.05.03
인지된 성능  (95) 2024.02.28
모바일 버전 섹션이동  (95) 2024.02.25
마우스 휠이벤트로 섹션 이동하기  (88) 2024.02.17
접근성 관련 자주 묻는 질문!  (89) 2024.02.14