TaeHyun Lim
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# 웹 성능 최적화 ###### tags: `FE` 페이지 로딩 시간은 모바일 사용자의 UX에 큰 영향을 주므로 모바일 기기 성능을 우선시로 사이트 디자인을 최적화해야 한다. 최적화 항목 - 페이지에서 로딩하는 자원의 수(이미지, 글꼴, HTML, CSS) - 로딩한 자원 파일의 크기 - 사용자가 느끼는 사이트의 체감 성능 브라우저는 웹 페이지를 렌더링하는 시간을 줄이기 위해 서버에 보내는 콘텐츠 요청을 병렬로 처리한다. 브라우저가 파일 요청하는 동시 연결의 수는 최대 6~8개이다. 컨텐츠의 파일 크기를 종류별로 측정하는 브라우저 플러그인 YSlow ## 렌더링 과정 ### 1. Dom Tree 생성 - HTML 파일을 받으면 Parsing과 Lexing 과정을 거쳐 Dom Tree로 만든다. - 바이트를 문자로 만든 뒤, 문자열을`<body>`같은 토큰으로 만드는 작업을 거친다. - 이를 통해 페이지를 표시하기 위해 모든 페이지 작업에 필요한 DOM 트리를 만든다. - HTML을 파싱하는 과정에서 스타일시트를 만나면 모든 작업을 일시 중지하고 서버에 파일을 요청한다. ### 2. CSSOM 생성 - 서버에서 받은 스타일시트 파일도 Tokenizer, Lexing을 거쳐 CSSOM을 생성한다. ### 3. Style(Render Tree 생성) - 만들어진 DOM과 CSSOM을 사용해 스타일을 매칭시켜 Render Tree를 구성한다. - Render Tree는 화면에 보이는 모든 요소의 위치와 크기를 계산하는데 사용한다. - 렌더링 트리는 페이지를 표시하는데 필요한 정보만 가지고 있다. 그래서 `display: none` 같은 것은 렌더링 트리에 포함하지 않는다. ### 4. Layout - Render Tree 노드의 정확한 위치와 크기를 계산한다. - 노드의 정확한 크기와 위치를 파악하기 위해 루트부터 노드를 순회하며, 이 과정으로 나온 위치와 크기를 픽셀 값을 Render Tree에 반영한다. - 예를 들어, `%`로 지정한 값은 Layout 과정을 거치면 `px`단위로 바뀐다. ### 5. Paint - 위치와 관계없는 색상, 투명도 같은 속성을 적용한다. - 픽셀로 변환된 결과는 개별 레이어로 관리되는데, `transform` 속성 등을 사용하면 요소가 레이어화된다. - 이렇게 실제 픽셀 값을 사용해 레이어들을 만드는 것을 Paint 과정이라고 한다. ### 6. Render - Paint 단계에서 생성된 레이어를 합성해 스크린을 업데이트한다. ## Reflow와 Repaint ### Reflow - DOM과 CSSOM을 사용해 Style, Layout, Paint, Render를 거치는 과정을 렌더링이라고 한다. - Render Tree는 JS에 의해 DOM이나 CSSOM 노드에 높이, 넓이, 위치 등의 CSS 속성이 변경되는 경우 재구성된다. - 전체 픽셀을 다시 계산해야 하기 때문에 부하가 커서 지양해야 한다. - 즉, 전체 픽셀을 다시 계산하는 Layout 다음 Paint 과정을 거쳐 성능에 영향을 준다. - height, width, left, top, font-size, line-height이 해당된다. ![](https://i.imgur.com/tW5oDXI.png) ### Repaint - 높이, 넓이, 위치 등의 기하하적 영향이 없는 경우 Layout 과정 없이 Paint가 일어난다. - 즉, 크기와 위치를 계산하는 시간이 없어졌기 때문에 Reflow가 아닌 대안으로 사용하길 지향한다. - background-color, color, visibility, text-decoration이 이에 해당된다. ![](https://i.imgur.com/jqcy121.png) ## Layout(Reflow) 최적화 - Layout을 최대한 적게하고 Repaint만 할 수 있도록 최적화하는 방법 ### 1. 강제 동기 레이아웃 최적화 - 원래 레이아웃은 비동기로 일어나지만 특정 상황에선 동기적으로 일어난다. - 특정 속성을 읽을 때 최신 값을 계산하기 위해 레이아웃은 동기적으로 발생한다. - `offsetHeight`, `offsetTop` 속성을 접근하면 값을 계산해야 하기 때문에 강제로 동기 레이아웃이 실행된다. ### 2. 레이아웃 스래싱 피하기 - `offsetHeight` 같은 속성을 많이 호출하는 것을 없애는 것 ```javascript= function resizeAllParagraphs() { const box = document.getElementById('box'); const paragraphs = document.querySelectorAll('.paragraph'); for (let i = 0; i < paragraphs.length; i += 1) { paragraphs[i].style.width = box.offsetWidth + 'px'; // 이 부분이 스래싱이 걸린다. } } // ===> 최적화 후 function resizeAllParagraphs() { const box = document.getElementById('box'); const paragraphs = document.querySelectorAll('.paragraph'); const width = box.offsetWidth; // 한번의 Layout만 수행시키고 값을 변수에 저장 for (let i = 0; i < paragraphs.length; i += 1) { paragraphs[i].style.width = width + 'px'; } } ``` ### 3. 가능한 최소 수의 하위 DOM 노드를 조작하게 함 - DOM 트리의 상위 노드에 Layout이 생기면 하위 노드에 모두 영향을 준다. - 그래서 변경 범위를 최소화 하면 Layout 범위를 줄일 수 있다. - 부모 요소의 높이가 가변적일 때, 자식 요소의 높이가 변경되면 부모 요소의 높이가 Layout 되고 이는, 부모의 형제 요소들에게도 영향을 준다. - 이럴 경우 부모 요소의 높이를 고정시켜 Layout을 최소화 시킨다. - 여러 개의 Inline 요소들이 한 줄에 놓여있으면, 하나의 width 변경이 다른 요소들에게 영향을 준다. ### 4. 요소를 숨긴 상태에서 수정하라. - `display: none`의 요소는 DOM 조작이나 스타일을 변경해도 Layout과 Repaint을 발생시키지 않는다. - 따라서 많은 수의 요소를 변경해야 하면 `display: none` 상태에서 요소를 변경하고 다시 보이게 해 Layout을 줄인다. - `visibility: hidden`은 Repaint가 발생하지 않지만, 공간을 차지하기 때문에 Layout이 발생한다. ### 5. CSS 규칙 수를 최소화한다. - 요소의 class가 변경되면 렌더링이 발생하므로 CSS가 복잡하면 안된다. - 사용하는 규칙이 적을수록 계산이 빨라진다. - 복잡한 선택자는 스타일 계산에 시간이 많이 걸려 최소화 시킨다. ### 6. DOM 노드 수를 줄이자. - DOM 트리가 깊을수록, 하나의 노드에 자식 노드가 많을 수록 DOM이 커진다. - 이 말은 즉, DOM을 갱신할 때 수행할 계산이 많아진다는 것 - 불필요한 Wrapper 요소들을 제거한다. ### 7. 애니메이션 최적화 - 프레임 처리 속도는 16ms(60fps)가 되야 자연스럽다. - 따라서 JS 실행 시간을 10ms 내에 수행시켜야 60fps를 완료시킬 수 있다. - 자바스크립트 API 보다 CSS가 빠르다.(애니메이션 최적화, GPU 사용) - `requestAnimationFrame` - 브라우저의 프레임 속도(주로 60fps)에 맞춰 애니메이션을 실행할 수 있게 한다. - setInterval, setTimeout과 다르게 프레임을 시작할 때 호출되기 때문에 일정한 간격으로 애니메이션을 수행할 수 있다. - 또한, 페이지가 보이지 않을 경우 콜백함수가 호출되지 않기 때문에 불필요한 동작을 하지 않는다. - `position: absoulte`로 애니메이션 영역이 주변 영역에 영향을 주지 않도록 할 수 있다. - `transform` 사용 - position, width, height 같은 기하적 변화를 야기하는 속성은에 적용하면 Layer로 분리되기 때문에 영향을 주는 요소가 제한되어 Layout과 Repaint를 줄일 수 있다. - 또한, 합성만 발생시키기 때문에 애니메이션 사용 시, 렌더링 속도가 향상된다. - left, top 같은 속성을 사용하면 모든 프레임마다 요소와 배경이 합성되어 시간이 더 걸리기 때문에, `transform: translate()`를 사용해야 한다. ```css= .animation-item { position: absolute; /* absolute */ top: 0; left: 0; width: 50px; height: 50px; background-color: red; animation: move 3s ease infinite; } @keyframes move { 50% { transform: translate(100px, 100px); } } ``` ## Block Resource - HTML 파싱이 일어날 때 CSS나 JS에 의해 파싱이 중단될 수 있다. - 중단을 일으킨 리소스를 Block Resource 라고 한다. - Block Resource는 Paint 과정을 지연시키기 때문에 주요 렌더링 경로 최적화 등으로 페인팅을 빠르게 만들어 로딩 속도를 개선해야 한다. ### Block Resource 최적화 #### CSS - Render Tree를 위해선 CSSOM의 생성이 완료되어야 한다. - 따라서, 렌더링이 차단되지 않도록 항상 HTML 문서의 최상단에 배치한다. - HTML Parsing과 CSSOM parsing이 병렬로 이루어질 수 있기 때문에, 스타일 시트를 가장 먼저 불러온 뒤, Tree 생성을 같이 수행하게 한다. - 또한, 특정 조건에만 사용하는 CSS는 미디어 쿼리를 사용해 불 필요한 블로킹을 방지할 수 있다. - CSS 파일 내의 `@import`는 그 스타일 시트를 병렬로 다운로드할 수 없기 때문에 로드 시간이 늘어나 무조건 지양한다. #### JS - 외부에서 가져온 스크립트는 다운로드하고 실행될 때까지 DOM 생성을 중지한다. - 그리고 스크립트 실행은 HTML Parsing을 중단시키기 때문에 Parsing이 다 끝난 후 스크립트를 실행하는 것이 좋다. #### 요청 리소스 수 줄이기 - 대체로 파일의 다운로드 시간보다 대기 시간이 더 길다. - 그래서 파일 요청을 줄이는 것이 성능상 중요하다. - 방법 1. 이미지 스프라이트 - 같은 웹 페이지에서 사용하는 이미지들을 하나의 파일로 묶는다. 2. CSS와 JS 번들 - 번들러를 사용해 여러 모듈 파일을 하나로 묶어 1개로 만든다. 3. 작은 이미지를 HTML, CSS로 대체 4. Internal Style 적용 5. 리소스 용량 줄이기 #### 리소스 용량 줄이기 - 중복 코드 제거하기 - lodash같은 유틸 라이브러리를 일반적으로 사용하면 유틸 함수 전체가 포함될 수 있다. ```javascript= import _ from 'lodash'; _.array(...); _.object(...); // ===> import array from 'lodash/array'; import object from 'lodash/fp/object'; array(...); object(...); ``` - HTML 마크업 최적화 - 태그의 중첩을 최소화 - 불필요한 태그, 공백, 주석 최소화 - 간결한 CSS 선택자 사용 - webpack 플러그인 같은 도구를 사용해 압축 ## 성능 측정 지표 ### 브라우저 기준의 측정 ![](https://i.imgur.com/vYTGeUm.png) - `DOMContentLoaded`나 `load` 발생 시점이 빠를 수록 성능이 좋다. - `DOMContentLoaded`나 `load` 발생 구간 폭이 좁을수록 성능이 좋다. - DOMContentLoaded는 HTML과 CSS 파싱이 끝나는 시점에 발생하는 이벤트, Render Tree를 구성할 준비가 된 상황 - load는 HTML에 필요한 모든 리소스가 준비된 시점의 이벤트 - 하지만 SPA같은 패러다임으로, 적은 양의 HTML을 가져와 위의 이벤트들이 빠르게 동작할 수 있지만 사용자에게 느린 성능의 페이지를 제공할 수도 있다. ### 사용자 기준의 측정 - 완료 이벤트와 상관없이 웹 페이지의 렌더링 성능을 측정한다. #### 주요 렌더링 경로 ![](https://i.imgur.com/BIVcyVT.png) ![](https://i.imgur.com/qSHY7Z5.png) 1. FP(First Paint) - 흰 하면에서 무언가가 처음으로 그려지는 순간 2. FCP(First Contentful Paint) - 텍스트나 이미지가 출력되기 시작한 순간 3. FMP(First Meaningful Paint) - 사용자에게 의미 있는 콘텐츠가 그려지는 첫 순간 - 콘텐츠를 보여주는데 필요한 CSS, JS 로드가 시작되고 스타일이 적용되어 주요 콘텐츠를 읽을 수 있다. 4. TTI(Time To Interactive) - JS의 초기 실행이 완료되어서 사용자가 직접 행동을 취할 수 있는 순간 ## 성능 측정 도구 - 크롬의 개발자 도구 패널 Network, Performance, Audits가 있다. ### 1. Performance 패널 ![](https://i.imgur.com/qi7nXtT.png) - 웹 페이지 로드 과정을 레코딩하고 그 단계마다 시간을 확인할 수 있다. ### 2. Network 패널 ![](https://i.imgur.com/OtJUpyL.png) - 웹 페이지 로드 과정에서 서버에게 요청된 리소스의 상태를 확인할 수 있다. #### 서버 요청 대기 시간 ![](https://i.imgur.com/eK4SHgP.png) 1. Queuing - 대기열에 쌓아둔 시간 - JS, CSS보다 우선순위가 낮아 대기한다. - TCP 소켓 대기, TCP 연결 초과 대기, 디스크 캐시 항목 작성 소요 시간 2. Stalled - 요청을 보내기 전의 대기 시간 - Queuing에서 쌓인 대기 시간 소모 - 프록시 협상에 드는 시간 3. DNS Lookup - DNS 조회에 소비된 시간 4. Initial Connection - TCP HandShake, 재시도 및 SSL 연결에 걸리는 시간 5. Waiting(TTFB) - 초기 응답을 기다리는데 소비한 시간(서버 왕복 시간) 6. Content Download - 리소스 실제 다운로드 시간 ### 3. Audits 패널 ![](https://i.imgur.com/8IfCwYS.png) 사용자 기준의 성능 측정 지표를 확인할 수 있다. 1. Metrics - 다양한 성능 측정 지표를 보여줌 - FCP, FMP, TTI 시점을 확인할 수 있다. - 웹 페이지가 화면에 그려지는 단계를 스크린샷으로 보여준다. 2. Opportunities - 최적화 가능한 리소스 목록을 보여준다. 3. Diagnostics - 리소스 최적화 외에 성능을 개선할 부분을 진단하고 해결 방안을 목록으로 보여줌 - 주요 렌더링 경로 최적화시, Critical Request Chains를 참조한다. 4. Passed audits - 성능 측정 기준과 통과 목록을 보여준다. <!-- Css는 렌더링을 멈추게 하는 자원 중 하나이므로 미디어 타입과 미디어 쿼리로 어떤 부분을 멈추지 않고 계속 렌더링할 것인지 표시한다. - 따라서, 반응형 웹을 만들 경우 크기에 따라 css 파일을 분리한 뒤, link 태그에 media 타입을 지정하는 것이 좋다. ```htmlmixed= <link rel="stylesheet" href="big-screens.css" media="(min-width: 1200px)" ``` - 비동기 방식으로 콘텐츠 가져오기 - 화면에 보이는 부분이ㅡ 콘텐츠를 먼저 표시하도록 우선순위 높이기 - CSS와 자바스크립트 로딩 모범 사례 따르기 - 재방문 사용자를 고려해 자원 캐싱하기 - 사용자가 페이지의 주요 기능을 최대한 빨리 사용할 수 있도록 보장하기 Jank(쟁크) - 웹페이지를 스크롤할 때 화면이 버벅이거나 건너 뛰는 현상 - 사용자가 페이지에 포함된 요소의 시각적 속성에 영향을 미치는 행동을 할 때 브라우저가 새로 화면을 그리면서 발생한다. ## 이미지 - 각 이미지 파일의 크기와 품질 사이의 균형점 찾기 - 사이트의 이미지 요청 횟수 줄이기 - 사이트의 이미지 생성 워크플로어를 최적화해서 성능 개선하기 ### JPEG - 사진이나 많은 색이 사용된 이미지에 사용 - 해상도 줄이기, Progressive JPEG로 내보내기, 이미지 노이즈 줄이기 - 사람 눈으로 인식하는 방식으로 사람이 눈치채지 못할 정도로 정보를 손실시고 압축한다. - 손실 압축 방식이므로 낮은 품질의 이미지를 저장하면 품질이 좋아 보이지 않는다. - 인접 픽셀 간 명암 대비가 크면 PNG같은 다른 포맷이 더 적합할 수 있다. - Progressive JPEG는 이미지 전체를 로딩하는 대신 낮은 품질로 즉시 표현해 기본 JPEG보다 화면에 더 빠르게 나타난다. - 하지만 Progressive JPEG는 스캔할 때 페이지를 렌더링하기 위해 스캔하는 수준의 CPU를 사용한다. ### GIF - 애니메이션에 사용 - dithering 줄이기, 색상 수 줄이기, 수평 패턴 사용, 수직 노이즈 줄이기 - dithering: 색상 간 전환이 더 부드럽게 보이게 도와주는 기능 - 색상이 다른 인접한 픽셀들을 검사해 부드러우면서 색상들이 조화롭게 보이도록 두 색 사이에 새로운 색을 골라 넣는다. - 무손실 파일 포맷이다. - 프레임 안에 256색까지만 포함하는 제한이 있다. - 그레이디언트 방향을 수직으로 변경하고 디더링을 하면 파일의 크기가 더 커진다. - 왜냐하면 GIF는 수평 중복성을 제거하는 압축 알고리즘을 사용하기 때문이다. - 그래서 수직으로 된 내용이나 노이즈를 추가하면 파일의 크기가 증가한다. - 그래서 수직으로 된 노이즈를 줄이면 GIF의 파일 크기에 긍정적인 도움이 된다. - 투명성을 지원하지만 파일 크기가 대체로 PNG보다 훨씬 크다. ### PNG-8 - 적은 색이 사용된 이미지에 사용 - dithering 줄이기, 색상 수 줄이기, 수평 및 수직 패턴 사용 ### PNG-24 - 일부 투명한 이미지에 사용 - 노이즈 줄이기, 색상 수 줄이기 p42 ## 마크업 디비티스 - 콘텐츠에 스타일을 적용하는 것처럼 사소한 이유로 HTML 내에 과하게 많은 요소가 사용된 상태 ### CSS - 사용하지 않는 스타일을 제거하거나 합친다. - 중복되는 스타일은 하나의 스타일로 합치고 차이나는 것만 별도로 작성한다. - css에 사용되는 이미지는 스프라이트로 사용하면 효율적이다. - 스타일 시트내 이미지를 CSS3 그레이디언트나 데이터 URI, SVG로 대체할 수 있는 곳을 찾는다. - CSS3 그레이디언트는 반복 배경 이미지를 대체하기 좋다. - 수정이 매우 쉬우며, 스타일 시트를 통해 재사용할 수도 있다. - specificity) 제거 - specificity는 브라우저가 어떤 CSS 규칙을 적용할지 결정하는데 도움이 되도록 셀렉터를 작성하는 방법 - 많은 특이성을 사용했다는 것은 스타일시트나 HTML 계층구조에서 개선할 여지가 있다는 지표이다. - 비효율적인 셀렉터와 !important는 CSS 파일을 커지게 하므로 지양하는 것이 좋다. CSS는 `<head>`에서 로딩한다. - 스타일시트를 페이지 하단 가까이에 두면 페이지의 렌더링을 방해한다. - 브라우저가 렌더링할 콘텐츠의 스타일이 변경되고 있을 때 페이지의 요소를 화면에 다시 그리는 일을 피하려고 하기 때문이다. - 하지만 `<head>`안에 넣으면 브라우저가 더 이상 스타일 정보를 찾지 않아 사용자에게 화면이 순차적으로 보이도록 할 수 있다. - CSS 파일은 크기는 30KB 이하로 잡는 것이 좋다. - 사이트가 크면 전체에 사용되는 스타일 시트 파일을 두고 각 페이지에 사용되는 스타일 시트를 별개로 두는 방법이 있다. 자바스크립트는 페이지의 하단에서 로딩한다. 자바스크립트는 명시적으로 비동기라고 선언하지 않으면 DOM 생성을 방해한다. HTML 파서가 script 태그를 발견하면 스크립트의 작업이 렌더링 트리를 변경할 수 있다고 판단하기 때문이다. ## 모바일 퍼스트 모바일을 중심으로 생각하면 디자인할 때 많은 도움이 된다. - 이 페이지의 가장 중요한 목적은 무엇인가 같은 핵심적인 질문을 고민하게 된다. - 사용자에게 가장 중요한 기능과 콘텐츠를 구분한다. - 재사용할 수 있는 디자인 패턴을 만들고 그것을 화면 크기에 따라 변경하는 방법을 생각한다. - 사이트 접근성, 인터넷이 느리거나 성능이 낮은 기기에서도 접속이 용이한지 생각하게 된다. -->

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully