구글 디벨로퍼 사이트의 글을 참고하여 작성된 글입니다 :)
브라우저가 동작하는 환경을 이해하기 위해서는 CPU, GPU, 메모리, 프로세스에 대한 이해가 있어야 합니다.
컴퓨터나 스마트폰에서 어플리케이션을 시작하면 운영체제의 메커니즘에 따라 CPU와 GPU가 앱을 실행합니다.
앱을 실행하면 프로세스가 생성되고, 프로그램은 해당 작업을 위해 스레드를 생성할 수도 있습니다. OS는 프로세스에 메모리 한 조각을 줘서 앱의 모든 상태 정보를 고유 메모리 공간에 저장할 수 있게 합니다. 앱을 종료하면 프로세스도 사라지고 OS가 메모리를 해제합니다.
프로세스는 다른 프로세스를 돌려서 별도의 작업을 수행하도록 OS에 요청할 수 있습니다. 두 프로세스 간 통신이 필요하다면 IPC(Inter Process Communication)을 이용합니다. 이 방식을 채택하면 워커 프로세스가 무응답 상태에 빠지더라도 다른 프로세스들을 종료할 필요 없이 해당 프로세스만 재시작할 수 있습니다.
CPU은 컴퓨터에서 뇌 역할을 합니다. CPU 코어는 다양한 작업들을 들어올 때마다 하나씩 처리합니다. 과거에는 대부분 CPU가 하나의 칩이었지만 최근에는 높은 성능을 발휘하는 멀티코어를 흔히 볼 수 있습니다.
GPU는 CPU와 달리 간단한 작업을 수많은 코어에서 동시에 처리하는 데 특화되어 있습니다. 원래는 그래픽을 처리하기 위해 개발되었으나, 최근에는 GPU 가속 연산으로 GPU 혼자서 더 많은 종류의 연산을 처리하도록 발전하고 있습니다.
프로세스는 어플리케이션의 실행 프로그램이고, 스레드는 프로세스 내부에서 프로그램을 실행하는 주체입니다.
웹 브라우저 또한 한 프로세스가 스레드를 많이 들고 있거나 스레드 몇 개를 가진 다수의 프로세스들이 IPC를 통해 통신하거나 하는 방법으로 동작할 것입니다.
여기서 웹 브라우저가 어떻게 작동해야 한다는 표준은 없지만, 크롬의 최근 아키텍처를 기반으로 살펴봅시다.
최상위 프로세스로, 다른 프로세스들을 조율합니다. 주소 창 및 이동 버튼을 포함한 어플리케이션의 크롬 부분을 제어하고, 네트워크 요청 및 파일 엑세스와 같은 웹 브라우저의 권한이 부여된 보이지 않는 부분을 제어합니다.
웹사이트가 디스플레이 될 때 탭 안의 모든 것을 담당합니다. 다수의 프로세스가 생성되어 각 탭마다 할당되는데, 최근까지 크롬은 각 탭마다 별도의 프로세스를 할당하였지만 현재는 iframe을 포함하여 각 사이트별로 프로세스를 가지도록 변경되었습니다.(사이트 격리)
플래시와 같은 웹사이트가 사용하는 플러그인을 담당합니다.
다른 프로세스와 분리된 GPU 작업을 제어합니다. 여러 앱의 요청을 제어하고 동일한 표면에 표시합니다.
확장 프로세스나 유틸리티 프로세스 등이 더 있습니다. 크롬에서 실행중인 프로세스를 보고 싶으면 작업 관리자에서 확인할 수 있습니다.
크롬은 여러 개의 렌더러 프로세스를 사용합니다. 탭 3개가 각각 별개의 렌더러 프로세스로 돌아간다고 가정했을 때, 한 탭이 무응답 상태가 되더라도 해당 탭만 닫아버리면 됩니다. 만약 모든 탭이 하나의 프로세스에서 실행된다면 모든 탭이 무응답 상태에 빠져버리게 되는 것입니다.
또 다른 이점은 보안 및 샌드박싱입니다. OS는 프로세스의 권한을 제한하는 방법을 제공하므로 브라우저는 특정 기능에서 특정 프로세스를 샌드박스할 수 있습니다. (ex. 렌더러 프로세스와 같은 사용자 입력을 처리하는 프로세스에 대한 파일 엑세스 제한)
프로세스들은 개별 메모리 공간을 소유하기 때문에 메모리 사용량이 더 많아지게 됩니다. 메모리를 절약하기 위해 크롬은 돌 수 있는 프로세스 개수에 제한을 두었습니다.
크롬은 현재 고성능 장치에서는 안정성을 위해 각 서비스를 별개의 프로세스로 분리하고, 저성능 장치에서는 서비스를 하나의 프로세스로 합쳐 메모리 점유를 낮추는 방식으로 변경하기 쉽게 구조를 변경중에 있습니다.
각 교차 사이트 iframe에 대해 별도의 렌더러 프로세스를 실행하는 기능입니다. 사이트 격리를 통한 프로세스 격리로 사이트를 분리함으로써 same-origin policy
를 준수할 수 있습니다.
사이트 격리는 단순히 별개의 렌더러 프로세스를 할당하는 것이 아닌, iframe들이 통신하는 방식을 근본적으로 변경시켰습니다. 별개의 프로세스들이 실행되는 한 페이지에서 개발자 도구를 사용하는 것도 기존과 차이가 없도록 백단에서 자연스럽게 구현되어야 했습니다. cmd+f/ctrl+f로 단어를 찾는 것도 전혀 다른 렌더러 프로세스를 뛰어 넘어야 하는 것입니다. 사이트 격리가 메이저 마일스톤으로 릴리즈된 이유입니다.
각 프로세스와 스레드가 브라우저의 여러 부분들을 어떻게 처리하는지 살펴보았으니, 웹사이트를 디스플레이하기 위해서는 어떻게 통신하는지 살펴봅시다.
웹 서핑을 생각해 봅시다. 브라우저에 주소를 치면, 브라우저는 인터넷에서 데이터를 받아 페이지를 표시합니다.
탭 밖의 것들은 모두 브라우저 프로세스가 담당합니다. 브라우저 프로세스는 버튼이나 입력창을 그리는 UI 스레드
, 인터넷에서 데이터를 수신하기 위해 통신 스택을 건드리는 네트워크 스레드
, 파일 등에 접근하기 위한 스토리지 스레드
등을 가지고 있습니다.
주소창에 url을 입력하는 순간 UI 스레드가 캐치합니다. UI 스레드는 우선 '검색어인지 url인지'부터 판단합니다. UI 스레드가 입력 문구를 파싱해서 검색 엔진에 보낼지, 유청한 페이지로 연결할지 결정합니다.
사용자가 엔터를 입력하면 UI 스레드가 컨텐츠를 받기 위해 네트워크 요청을 초기화합니다. (이 때 네트워크 스레드는 http 301과 같은 리다이렉션 헤더를 수신할 수도 있습니다. 그러면 새로운 url 요청을 초기화합니다.)
응답 바디(payload)가 들어오기 시작하면, 네트워크 스레드가 스트림의 처음 몇 바이트를 확인합니다. Content-Type 헤더가 데이터 타입을 알려주지만, 빠지거나 틀릴 수 있기 때문에 MIME Type 스니핑을 수행합니다. 소스 코드의 주석에서 content-type과 payload를 어떻게 처리하는지 알 수 있습니다.
응답이 html 파일이면 렌더러 프로세스에 데이터를 넘기고, zip 또는 다른 형식의 파일이라면 다운로드 메니저에 데이터를 넘깁니다. 이 때 safe browsing 체크를 통해 악성 사이트인지 확인하고, CORB(Cross Origin Read Blocking) 체크를 통해 민감한 cross-site 데이터가 렌더러 프로세스에 도달하지 못하게 합니다.
모든 확인 작업이 끝나고 네트워크 스레드가 요청된 사이트로 이동해야 함을 확신하면 UI 스레드에게 데이터가 준비되었다고 알려줍니다. 그러면 UI 스레드는 웹 페이지 렌더링을 담당할 렌더러 프로세스를 찾습니다.
step 2에서 이미 어느 사이트로 가야할지 알고 있습니다. UI 스레드는 네트워크 요청과 함께 렌더러 프로세스를 찾거나 시작하려 합니다. 모든 게 계획대로라면 네트워크 스레드가 데이터를 수신했을 때 렌더러 프로세스는 이미 대기하고 있을 것입니다. (만약 탐색 도중 cross-site로 리다이렉트된다면 준비된 프로세스는 사용되지 않고 다른 프로세스가 필요하게 됩니다.)
데이터와 렌더러 프로세스가 준비되면, 탐색을 커밋하기 위해 브라우저 프로세스에서 렌더러 프로세스로 IPC가 전송됩니다. 또 데이터 스트림을 전달하여 렌더러 프로세스가 html 데이터를 계속 받을 수 있게 합니다. 렌더러 프로세스에서 커밋이 확인되면 프라우저 프로세스는 탐색을 완료하고 문서 로딩 단계를 시작합니다.
이 시점에서 주소창이 갱신되고 security indeicator와 사이트 설정 UI가 새 페이지의 사이트 정보를 반영되고 탭의 세션 이력이 갱신됩니다. 탭/세션 복구 기능을 위해, 탭이나 윈도우를 닫을 때 세션 이력은 디스크에 저장됩니다.
탐색이 커밋되고 나면, 렌더러 프로세스가 리소스 로딩과 페이지 렌더를 지속합니다. 렌더링을 끝내면 브라우저 프로세스에 IPC를 반환합니다. 이 시점에서 UI 스레드는 탭의 로딩 스피너를 정지시킵니다. 클라이언트 사이드의 JS는 이 시점 이후에도 계속 추가적인 리소스를 로드하거나 새로운 뷰를 렌더할 수 있습니다.
beforeunload
는 다른 사이트를 방문하거나 탭을 닫을 때 "이 사이트에서 나가시겠습니까?" 팝업을 띄울 수 있습니다. JS 코드를 포함한 탭 안의 모든 것들은 렌더러 프로세스가 처리하므로, 브라우저 프로세스는 새 탐색 요청이 들어올 때 렌더러 프로세스를 체크할 필요가 있습니다.
무조건적으로 beforeunload 이벤트 핸들러들을 추가하지는 마세요. 탐색을 시작하기도 전에 해당 이벤트에 대한 헨들러를 실행시켜야 하기 때문에 대기 시간이 더 생깁니다. 페이지에 작성한 데이터가 소실될 수 있음을 경고하는 등, 반드시 필요한 경우에만 추가하세요.
렌더러 프로세스가 탐색 과정을 초기화하면 렌더러 프로세스는 우선 beforeunload 핸들러를 체크합니다. 이후 탐색 초기화 프로세스는 동일합니다. 차이점은 렌더러 프로세스가 탐색 요청을 브라우저 프로세스에게 토스한다는 것입니다. 현재 렌더러 프로세스가 unload 같은 이벤트를 처리하는 동안 별개의 렌더러 프로세스가 새 탐색을 처리하기 위해 호출됩니다.
탐색 프로세스의 최근 변경점은 서비스 워커의 도입입니다. 서비스 워커는 코드에 네트워크 프록시를 작성할 수 있는 수단입니다. 개발자로 하여금 로컬에 캐시할 데이터와 네트워크로부터 받아올 데이터를 컨트롤할 권한을 더 가지게 합니다. 서비스 워커가 페이지를 캐시에서 로드하도록 세팅되면 네트워크에서 데이터를 받아올 필요가 없어집니다.
그렇다면 서비스 워커는 렌더러 프로세스데어 돌아가는 JS 코드인데, 어떻게 탐색 요청이 들어오자마자 사이트에 서비스 워커가 있다는 것을 브라우저가 알 수 있을까요?
서비스 워커가 동록되면 서비스 워커 스코프가 레퍼런스로 취급됩니다. 탐색을 시작할 때, 네트워크 스레드는 등록된 서비스 워커 스코프와 도메인을 비교하여, 동일한 url에 서비스 워커가 등록되어 있으면 UI 스레드가 해당 서비스 워커 코드를 실행하기 위해 렌더러 프로세스를 찾게 됩니다. 서비스 워커는 데이터를 캐시에서 로드할테니, 네트워크 데이터 요청을 다 날리거나 새로운 리소스를 요청하게 됩니다.
서비스 워커가 결국 네트워크에 데이터를 요청하기로 결정한다면 브라우저 프로세스와 렌더러 프로세스간의 이러한 반복 행위는 딜레이의 요인이 될 수 있습니다. 선제 탐색은 서비스 워커의 시작과 동시에 리소스들을 병행 로딩하여 이 과정을 빠르게 하는 메커니즘입니다. 이런 요청들에 헤더를 표기하여, 서버가 전체 문서 대신에 갱신된 내용만 보내는 등 다른 콘텐츠를 보낼지 결정하게합니다.
그렇다면 렌더러 프로세스 내부에서는 무슨 일이 일어나는지 살펴봅시다.
렌더러 프로세스는 브라우저 탭 안에서 일어나는 모든 일들을 담당합니다. 렌더러 프로세스 안의 메인 스레드
가 개발자가 구현한 대부분의 코드를 처리합니다. (웹 워커 혹은 서비스 워커를 사용하면 워커 스레드
가 코드 일부분을 처리합니다.) 컴포지터 스레드
와 레지스터 스레드
또한 페이지를 효율적이며 매끄럽게 렌더라기 위해 실행됩니다.
렌더러 프로세스가 탐색을 위한 커밋 메세지를 받고 html 데이터를 받기 시작할 때, 메인 스레드는 htmml을 파싱하여 DOM(Document Object Model)로 변환합니다. DOM은 페이지에 대한 브라우저의 내부 표현일 뿐만 아니라 개발자가 JS를 통해 상호작용할 수 있는 데이터 구조 및 API입니다.
html 문서를 돔으로 파싱하는 방법을 html 표준에 정의되어 있습니다. (html을 브라우저에 넘길 때 잘못된 마크업에 대해 에러가 발생하지 않는 이유는 html 표준이 이러한 에러들을 적절하게 다루도록 설계되었기 때문입니다.)
웹 사이트는 이미지, CSS, JS와 같은 외부 리소스를 사용합니다. 이러한 파일들은 네트워크 혹은 캐시로부터 로드되어야 합니다. 파싱 중 그런 리소스를 찾을 때마다 요청할 수도 있지만, 속도를 높이기 위해 preload scanner
가 동시에 실행됩니다. <img>
혹은 <link>
가 있는 경우, preload 스캐너는 html 파서에 의해 생성된 토큰들을 보고 네트워크 스레드에게 요청을 보냅니다.
html 파서는
<script>
태그를 발견하면 파싱을 잠시 멈추고 JS를 로드, 파싱, 실행해야 합니다. JS가 전체 돔 구조를 바꿀 수 있기 때문에 다시 html 문서를 다시 파싱하기 전에 JS를 기다려야 합니다.
자바스크립트가 문서를 변경하지 않는다면, <script>
의 async/defer 속성을 사용하여 JS를 비동기적으로 로드하고 실행하여 파싱을 막지 않게 할 수 있습니다. <link rel="preload">
로 리소스가 현재 탐색에서 반드시 필요하고 여러분이 가능하면 빨리 다운로드하고 싶다는 것을 알릴 수도 있습니다.
CSSdㅔ서 페이지 요소들에 대한 스타일을 정의할 수 있습니다. 메인 스레드는 CSS를 파싱하여 각 돔 노드에 대한 스타일을 결정합니다. 이는 CSS selector에 기반하여 각 요소들에 어떤 스타일이 적용되었는지에 대한 정보입니다. 이러한 정보는 개발자 도구의 computed 섹션에서 볼 수 있습니다.
CSS를 전혀 활용하지 않더라도 브라우저가 기본 스타일 시트를 가지고 있기 때문에 각 돔 노드는 스타일을 가지고 있습니다.
레이아웃은 요소들의 기하학적인 구조를 찾는 과정입니다. 메인스레드는 돔과 스타일을 따라가며 x,y 좌표 및 bounding box의 크기와 같은 정보를 가지는 레이아웃 트리를 생성합니다. 레이아웃 트리는 페이지에 보이는 정보만을 담고 있습니다. (display: none
이 적용된 요소는 레이아웃 트리에 포함되지 않습니다. 가상요소 선택자의 경우에는 돔 트리에는 없지만 레이아웃 트리에는 포함됩니다.)
요소들을 어떤 순서로 그릴지 판단해야 합니다. z-index
는 임의의 요소들에 설정될 수 있는데, 이 경우 html에 작성된 순서로 그리면 잘못된 렌더링이 발생합니다.
페인트 단계에서 메인 스레드는 레이아웃 트리를 따라가 페인트 기록을 생성합니다.
렌더링 파이프라인 업데이트는 비용이 많이 듭니다. 만일 요소들을 애니메이션으로 표현할 경우, 이러한 동작을 매 프레임마다 실행해야 합니다. 대부분의 디스플레이는 60fps로 스크린을 새로고침합니다.
렌더링 연산은 메인 스레드에서 실행되기 때문에, 이 과정이 JS를 실행한는 것을 방해할 수 있습니다. 이 경우, requestAnimationFrame()을 이용하여 매 프레임마다 실행하는 것을 미리 설정할 수 있습니다.
페이지에 대한 정보를 스크린의 픽셀로 바꾸는 것을 레스터라이징
이라고 합니다. 이를 처리하는 단순한 벙법은 화면에 보이는 영역을 레스터하는 것입니다. 사용자가 스크롤을 내리면, 레스터된 프레임을 움직이고 더 레스터링하여 부족한 부분을 메꿉니다. 하지만 모던 브라우저는 컴포지팅
이라는 더 세련된 방식으로 동작합니다.
컴포지팅은 한 페이지의 부분들을 여러 레이어로 나누고 그 것들을 각각 레스터하며 컴포지터 스레드
에서 페이지를 합성하는 기술입니다. 만약 스크롤이 발생하면, 레이어들이 이미 레스터되었기 때문에 새로운 프레임을 합성하면 됩니다. 어떻게 웹 사이트가 여러 레이어로 나뉘는지는 개발자 도구의 Layers panel
에서 볼 수 있습니다.
어떤 요소들이 어떤 레이어에 있어야 하는지 알기 위해, 메인 스레드는 레이아웃 트리를 순회하여 레이어 트리를 생성합니다. (개발자 도구 성능 탭의 Update Layer Tree
) 만약 별도의 레이어에 있어야만 하는 페이지의 어떤 부분(ex. 슬라이드되어 들어오는 사이드 메뉸)이 아직 처리되지 않은 경우에는 CSS 속성 will-change
를 이용하여 브라우저에게 미리 알려줄 수 있습니다.
지나친 수의 레이어에 대해 합성하는 것은 매우 느리므로 이는 렌더링 성능을 따질 때 아주 중요합니다. (컴포지터만 사용하는 속성만을 사용하고 레이어 수 관리하기)
레이어 트리가 생성되고 페인트 순서가 결정되고 나면, 메인 스레드는 컴포지터 스레드에게 정보를 커밋합니다. 그 후 컴포지터 스레드가 각 레이어를 레스터라이즈합니다. 컴포지터 스레드는 레이어들을 여러 타일로 쪼개고 각 타일을 다수의 레스터 스레드에게 보냅니다. 레스터 스레드들은 각 타일을 레스터라이즈하고 GPU 메모리에 저장합니다.
컴포지터 스레드는 서로 다른 레스터 스레드들에 대해 우선 순위를 정할 수 있어 화면 안에 보이는 것들이 먼저 레스터될 수 있습니다. 또한 한 레이어는 다른 해상도에 따라 다수의 타일을 가질 수 있는데, 이는 줌인 동작을 처리하기 위함입니다.
타일들이 레스터되면, 컴포지터 스레드는 쿼드 군집이라 하는 타일 정보들을 모아 컴포지터 프레임을 생성합니다. 메모리에서 타일의 위치 및 합성을 고려하여 타일을 그릴 페이지의 위치와 같은 정보를 포함시키는 과정이 쿼드 그리기
이며, 컴포지터 프레임
은 한 페이지의 프레임을 나타내는 쿼드 군집의 컬렉션입니다.
컴포지터 프레임은 IPC를 통해 브라우저 프로세스로 넘어갑니다. 이 때, UI 변화에 따라 다른 컴포지터 프레임이 UI 스레드 또는 다른 렌더러 프로세스들에 의해 추가될 수 있습니다.
컴포지터 프레임들은 GPU에 보내져 화면에 보여집니다. 만약 스크롤 이벤트가 발생하면 컴포지터 스레드는 GPU에 보낼 다른 컴포지터 프레임을 생성합니다.
컴포지팅의 장점은 메인 스레드의 개입 없이 수행된다는 것입니다. 컴포지터 스레드는 스타일 계산이나 JS 실행을 기다릴 필요가 없습니다. 이것이 컴포지팅만 하는 애니메이션이 부드러운 성능을 위한 가장 좋은 방법으로 여겨지는 이유입니다. 만약 레이아웃 또는 페인트가 다시 계산된다면 메인 스레드가 관여해야 합니다.
그렇다면 어떻게 컴포지터가 사용자 입력 발생시 부드러운 인터렉션을 가능하게 하는지 살펴봅시다.
브라우저의 관점에서 입력은 사용자의 모든 제스처를 의미합니다. 화면에서 사용자 제스처가 발생하면 브라우저 프로세스는 처음에 제스처를 받습니다. 그러나 탭 내부의 내용은 렌더러 프로세스에서 처리되기 때문에 브라우저 프로세스는 제스처가 발생한 위치만 인식합니다. 브라우저 프로세스는 touchstart
와 같은 이벤트 유형과 해당 좌표를 렌더러 프로세스로 전송합니다. 렌더러 프로세스는 이벤트 대상을 찾고 연결된 이벤트 리스너를 실행하여 이벤트를 적절하게 처리합니다.
페이지에 연결된 이벤트 리스너가 없는 경우 컴포지터 스레드는 메인 스레드와 완전히 독합적으로 새 컴포지터 프레임을 생성할 수 있습니다. 하지만 일부 이벤트 리스너들이 이 페이지에 들어있다면 컴포지터 스레드는 해당 이벤트를 처리해야 하는지의 여부를 어떻게 알 수 있을까요?
JS 실행은 메인 스레드의 작업이므로, 페이지가 구성될 때 컴포지터 스레드는 이벤트 핸들러가 연결된 페이지의 영역을 non-fast scfrollable 영역
으로 표시합니다. 컴포지터 스레드는 이 정보를 통해 이벤트가 해당 영역에서 발생할 경우 메인 스레드로 입력 이벤트를 전송할 수 있습니다. 입력 이벤트가 이 영역 외부에서 발생하는 경우 컴포지터 스레드는 메인 스레드를 기다리지 않고 새 프레임을 합성합니다.
이벤트 핸들러를 작성할 때 주의해야할 점이 있습니다. 이벤트 위임은 모든 요소에 대해 하나의 이벤트 핸들러만 작성하면 되므로 이벤트 위임은 매력적입니다.
document.body.addEventListener(('touchstart', e) => {
if (e.target === area) {
e.preventDefault();
}
});
하지만 브라우저 관점에서 위의 코드를 살펴보면 전체 페이지가 non-fast scfrollable 영역
으로 표시됩니다. 즉, 앱이 페이지의 특정 부분으로부터의 입력에 신경쓰지 않더라도 컴포지터 스레드는 메인 스레드와 통신하여 입력 이벤트가 발생할 때마다 기다려야 합니다. 따라서 컴포지터의 부드러운 스크롤 기능이 해제됩니다.
이러한 문제를 완화하기 위해 이벤트 리스너의 passive: true
옵션을 사용할 수 있습니다. 이는 메인 스레드에서 이벤트를 계속 듣고 싶지만 컴포지터가 새 프레임을 합성할 수도 있다는 것을 브라우저에게 알려줍니다.
document.body.addEventListener(('touchstart', e) => {
if (e.target === area) {
e.preventDefault();
}
}, {passive: true});
페이지에 스크롤 방향을 수평 스크롤로만 제한하려는 상자가 있다고 가정해봅시다.
포인터 이벤트에서 passive: true
옵션을 사용하면 페이지 스크롤이 부드러워질 수 있지만, 스크롤 방향을 제한하기 위해 기본값을 방지하려는 시점에 수직 스크롤이 시작되었을 수 있습니다.event.cancelable
로 한번 더 확인할 수 있습니다.
document.body.addEventListener(('pointermove', e) => {
if (e.cancelable) {
e.preventDefault(); // block the native scroll
/*
* do what you want the application to do here
*/
}
}, {passive: true});
아니면 touch-action
과 같은 CSS 규칙으로 이벤트 핸들러를 완전히 제거할 수도 있습니다.
#area {
touch-action: pan-x;
}
컴포지터 스레드가 메인 스레드로 입력 이벤트를 전송할 때 가장 먼저 실행해야 할 것은 이벤트 대상을 찾기 위한 hit 테스트입니다. hit 테스트는 렌더링 프로세스에서 생성된 페인트 레코드 데이터를 사용하여 이벤트가 발생한 지점 좌표 아래에 무엇이 있는지 확인합니다.
입력을 위해 일반적인 터치 스크린 장치는 60~120fps 터치 이벤트를 제공하고, 일반 마우스는 100fps 이벤트를 제공합니다. 입력 이벤트의 충실도가 화면 새로 고침(60fps)보다 높습니다.
touchmove
와 같은 같은 연속적인 이벤트가 120fps로 메인 스레드로 전송되면 화면이 새로 고쳐지는 속도에 비해 과도한 양의 hit 테스트와 JS 실행을 야기할 수 있습니다.
메인 스레드에 대한 과도한 호출을 최소화하기 위해 크롬은 연속 이벤트(wheel
, mousewheel
, mousemove
, pointermove
, touchmove
)를 병합하고 다음 requestAnimationFrame
직전까지 디스패치를 지연시킵니다.
(keydown
, keyup
, mouseup
, mousedown
, touchstart
, touchend
같은 이산 이벤트는 즉시 디스패치됩니다.)
getCoalescedEvents
로 intra-frame 이벤트 가져오기대부분의 웹 앱에서 병합된 이벤트는 좋은 UX를 제공하기에 충분해야 합니다. 그러나 앱을 그리고 touchmove
좌표를 기반으로 경로를 설정하는 경우 부드러운 선을 그리기 위한 중간 좌표가 손실될 수 있습니다. 이 경우 포인터 이벤트의 getCoalescedEvents
메서드를 사용하여 병합된 이벤트에 대한 정보를 가져올 수 있습니다.
window.addEventListener(('pointermove', e) => {
const events = e.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});
Girlfrien's Experience, Wife's Experience. Similarly, you can spend your loneliness in a room with a female companion. Seema is the only Karol Bagh Escorts Agency that can provide you genuine female Escorts. This is because Seema has been providing Escorts services in Karol Bagh for 10 years.
잘 보고 많이 배워갑니다~!