React debounce in useEffect
리액트에서 useEffect 내부 debounce 를 구현하는 패턴에 대해서 학습하고 정리|an hour ago
React debounce in useEffect
useEffect 내부에서 클린업 함수를 실행함과 동시에 다음 이펙트에 대한 처리를 함으로써 debounce 기능과 동일한 효과를 보도록 구현할 수 있다.
import { useEffect, useState } from 'react'
function ChildInput({ onChange }) {
const [input, setInput] = useState('')
useEffect(() => {
const timer = setTimeout(() => {
onChange(input)
}, 500)
return () => clearTimeout(timer)
}, [inpuㅃ onChange])
return (
<input type="text" value={input} onChange={e => setInput(e.target.value)} />
)
}
위 예시를 보면 effect 내부 setTimeout 함수로 넘긴 콜백 함수는 이펙트가 여러 번 발생해도 마지막 이벤트가 발생한 후 500ms가 지나면 한번만 실행된다. 원리에 대해서 짚고 넘어가면 좋을 것 같다.
- setTimeout 함수의 인자로 콜백 함수를 넘긴다. (effect 내부 실행)
- (상태 업데이트) 다음 effect 내부가 실행되기 전 클린업 함수가 먼저 실행
- 기등록된 setTimeout 예약 종료
- 다음 effect 에서 반복..
따라서 debounce 를 사용한 것과 동일한 효과를 볼 수 있다.
번외) 비동기(Promise) 경쟁
꽤 많이 쓰이는 패턴인 것 같다. effect 내부 비동기 함수 호출이 있는 경우 상태가 변경되어 새 effect 가 실행되기 전 비동기 호출이 아직 종료되지 않았다면 그리고 effect 내부 또 다른 상태에 대한 업데이트가 동반되는 경우, 이전 effect 의 비동기 함수 호출보다 새 effect 비동기 함수 호출이 먼저 종료되어 이전 상태가 더 늦게 반영이 되어 업데이트되는 경우가 종종 일어날 수 있다.
useEffect(() => {
let ignore = false // 플래그 설정
const fetchData = async () => {
// 디바운스된 값으로 API 호출
const result = await api.search(debouncedValue)
// 응답이 왔을 때, 이미 새로운 요청이 시작되었다면(ignore가 true면)
// 상태를 업데이트하지 않음
if (!ignore) {
setResults(result)
}
}
fetchData()
return () => {
ignore = true // 다음 렌더링(또는 언마운트) 시 플래그를 true로 변경
}
}, [debouncedValue])
플래그를 하나 두고 플래그 상태에 따라 처리 방법을 달리한다. 플래그는 클린업 함수에서 초기화한다.
Node Alpine 이미지에서 lightningcss 관련 바이너리 미설치로 인한 빌드 실패 트러블슈팅
Node Alpine 이미지에서 lightning 관련 바이너리를 찾을 수 없다는 메시지와 함께 마주한 빌드 에러, 트러블슈팅한 기록과 내용을 정리|5 days ago
Node Alpine 이미지에서 lightningcss 관련 바이너리 미설치로 인한 빌드 실패 트러블슈팅
vite build 명령어로 프론트엔드 프로젝트를 빌드하다가 lightningcss 관련 에러를 마주했다.
Error: Cannot find module '../lightningcss.linux-arm-musl.node
내가 설치한 모듈은 아니고, vite, @tailwindcss/vite 의존성 내부에서 사용중인 모듈인듯 하다. 뭐하는 애인지는 알고 있었는데, CSS 파싱, 변환, 압축 등 CSS 관련 유틸리티 기능을 제공하는 라이브러리다. 다만 Rust 코드로 작성되어 있어 npm 에서는 바이너리 형태로 제공된다.
따라서, 운영체제별로 설치되는 라이브러리가 다르다. 윈도우는 윈도우 전용 라이브러리, 리눅스는 리눅스 전용 라이브러리가 있다. 단, 리눅스 전용 라이브러리라고 하더라도, 사용하는 표준 운영체제 라이브러리 종류에 따라 바이너리의 종류도 달라진다.
가령, 일반적인 리눅스(우분투나 데비안 등)에서는 glibc라는 GNU 프로젝트 진영의 표준 C 라이브러리를 사용한다. 대부분의 리눅스가 여기에 포함되기 때문에 대부분 glibc 기준으로 바이너리도 빌드된다. 문제가 된 건 node:22-alpine 이미지를 사용해서 glibc가 아닌 musl를 표준 C 라이브러리로 사용하는데, 요 바이너리를 다운로드 받지 못한건지 계속 찾을 수 없다는 에러를 마주했다.
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
"node_modules/@rollup/rollup-linux-arm64-musl": {
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
"node_modules/@rollup/rollup-linux-x64-musl": {
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
"@tailwindcss/oxide-linux-x64-musl": "4.1.18",
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
"lightningcss-linux-arm64-musl": "1.30.2",
"lightningcss-linux-x64-musl": "1.30.2",
"node_modules/lightningcss-linux-arm64-musl": {
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
"node_modules/lightningcss-linux-x64-musl": {
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
"@rollup/rollup-linux-arm-musleabihf": "4.53.2",
"@rollup/rollup-linux-arm64-musl": "4.53.2",
"@rollup/rollup-linux-riscv64-musl": "4.53.2",
"@rollup/rollup-linux-x64-musl": "4.53.2",
package-lock.json 파일 내부 musl 키워드 검색 결과인데, 분명 lightningcss 바이너리가 musl 대상으로도 지정이 되어있다. 이러면 설치가 되어있어야 정상일텐데 무언가 잘못된 걸까. node_modules 를 지우고 클린 설치 이후에도 동일한 에러가 계속해서 발생했다. 물론 alpine 이미지 말고 slim 이미지를 사용하면 바로 해결되는 문제이긴 하지만, 이미지를 바꾸지 않고도 이슈를 해결할 수 있는지가 궁금해져서 여러 방법을 시도해봤다.
- lock 파일 지우고 설치
- 노드 버전 바꾸기
apk add --no-cache libc6-compat- musl 환경에서 glibc 의존성이 있는 부분들을 해결해준다.
.dockerignorenode_modules 등록
libc6-compat 설치는 효과가 있었다. 다만 이미지를 바꿔버리면 되는 깔끔한 대안이 있어서 slim 이미지로 변경해서 사용했다. node:22-alpine 이미지와 node:22-slim 이미지의 용량 차이는 100MB 조금 안되게 차이가 난다.
border 에 gradient 적용하기
border 에 gradient 적용한 느낌 부여하기|8 days ago
border 에 gradient 적용하기
요즘 LLM을 활용한 도구 및 화려한 웹 서비스들을 보면 프리미엄 버튼이라고 해서 눈에 띄는 UI 적인 버튼 요소들이 많이 보이는데, 주로 border 에 gradient 효과를 적용시킨 버튼들이 많이 보인다. 이런 효과를 적용시킬 수 있는 CSS 속성에 대해서 알아보고 정리한다.
See the Pen border gradient by n2ptune (@n2ptune) on CodePen.
새 탭을 열어서 확인
뚝딱 해버렸는데, 핵심은 그라데이션 적용 영역을 border-width 로 주고, transparent 투명색으로 칠한다.
CSS background 속성은 여러 레이어를 겹겹이 쌓을 수 있는데, 먼저 선언된 속성이 항상 상위 레이어에 위치한다. 그 뒤에 선언된 항목들은 아래 레이어에 쌓인다. 따라서 상위 레이어 부분이 가장 먼저 보인다. linear-gradient 함수 사용 뒤, <box> 값으로 padding-box, border-box 가 사용됬는데, <box> 값이 하나만 주어진 경우, background-origin, background-clip 모두 그 값으로 설정한다는 뜻이다.
background-origin배경 이미지를 어느 기준으로 배치할지 결정한다.<box>값이 온다.background-clip<box>값까지 포함하여 배경 이미지를 표시한다.
따라서 배경색은 padding-box 에 위치하고, gradient 색상은 border-box 영역에만 입혀 border 에 gradient 효과를 적용하는 방법이다.
이 방법을 응용한 방법은 아니지만 border 에 gradient 색상을 입히고 회전시키는 재밌는 방법도 https://rgy0409.tistory.com/5049 아주 잘 설명한 블로그 글이 있어서 참고해보면 재미있을 것 같다.
자바스크립트 함수 실행 시간 측정
자바스크립트에서 함수 실행 시간을 측정할 수 있는 다양한 방법에 대해서 정리|2 months ago
자바스크립트 함수 실행 시간 측정
함수 실행 시간을 측정할 수 있는 여러 방법에 대해서 학습하고 정리한다. 함수 실행 시간 측정을 하는 방법에 대해서 알고 있으면, 성능 최적화 및 벤치마킹 / 디버깅 등에 도움이 될 수도 있다.
Date.now()
Date 객체의 now 메서드를 사용하면 밀리초를 리턴받을 수 있는데, 이걸 이용해 함수 실행 시간을 측정할 수 있다.
function run() {
const start = Date.now()
for (let i = 0; i < 1e6; i++) {
Math.sqrt(i)
}
const end = Date.now()
console.log(`Execution time: ${end - start} ms`)
}
// result: Execution time: XX ms
now 메서드는 1970년 1월 1일 0시 0분 0초부터 현재까지 경과된 시간을 밀리초 단위로 리턴한다. 이를 통해서 함수 시작시 한번 찍고, 종료부에 한번 찍고 종료 시간과 시작 시간을 빼주면 함수 실행 시간을 측정할 수 있다.
console.time() / console.timeEnd()
Date.now 메서드보다 더 간결한 방식으로, Date 객체와 마친가지이지만 브라우저 및 Node.js 환경 모두에서 사용 가능하다.
function run() {
console.time('Execution Time')
for (let i = 0; i < 1e6; i++) {
Math.sqrt(i)
}
console.timeEnd('Execution Time')
}
// result: Execution Time: XX ms
메서드의 인수로 문자열을 받는데, 라벨링을 한다고 보면 된다. time 메서드와 timeEnd 메서드에 동일한 라벨을 넘겨주면, 해당 라벨에 대한 실행 시간을 측정하여 출력해준다. now 메서드와 마찬가지로 밀리초 단위로 반환된다.
process.hrtime
process 객체가 존재하는 Node.js 환경에서만 사용 가능한 방법이나, 매우 정밀하게 측정할 수 있는 특징이 있다.
function run() {
const start = process.hrtime()
for (let i = 0; i < 1e6; i++) {
Math.sqrt(i)
}
const end = process.hrtime(start)
console.log(`Execution time: ${end[0]}s ${end[1] / 1e6} ms`)
}
// result: Execution time: 0s XX ms
hrtime 은 나노초 단위로 반환한다. 반환하는 타입이 [number, number] 형태이고, 첫번 째는 초를 뜻하고 두번째는 나노초를 의미한다.
performance.now()
위 hrtime 과 동일하게 정밀한 측정이 가능하다는 특징이 있고, 브라우저 환경과 Node.js 환경 모두에서 사용 가능하다. 반환하는 단위는 소수점을 포함한 밀리초다. (hrtime 이 더 세밀한 단위)
function run() {
const start = performance.now()
for (let i = 0; i < 1e6; i++) {
Math.sqrt(i)
}
const end = performance.now()
console.log(`Execution time: ${end - start} ms`)
}
// result: Execution time: XX.XXXX ms
다른 것들과 특징은, 소수점 아래 자릿수가 세밀하여 마이크로초까지 측정할 수 있다는 특징이 있다.
헷갈리는 맛 쿠키
맨날 봐도 맨날 헷갈리는 쿠키에 관련된 정리 내용|2 months ago
헷갈리는 맛 쿠키
인증 세션같은 것을 개발하다 보면 늘 접하는 개념이 쿠키인데, 알고있는 것 같으면서도 헷갈리는 부분이 많아 늘 문서를 찾아보게 된다. 이번에 또 접한 쿠키 관련 이슈가 있어 정리하고 기록해보면 좋을 것 같다.
같은 도메인을 결정하는 기준
쿠키는 도메인 단위로 볼 수 있다. 예를 들어, abc.com 에서 설정한 쿠키의 속성 및 값이 def.com 에서 설정한 쿠키의 속성 및 값과 동일해도, 서로 다른 쿠키라고 보는 것이다. 하지만 여기서 동일한 도메인이라는 개념이 헷갈리기 쉬운데, 여기서 짚고 넘어가면 좋을 것 같다. 정리하기 전, 쿠키에서 도메인이 동일한 경우 SameSite 라고 지칭하면 될 것 같다.
abc.com과abc.abc.com은 SameSite로 본다.abc.com과abc.com:8080은 SameSite로 본다.abc.com과abc.co.kr은 SameSite로 보지 않는다.github.io와my.github.io는 SameSite로 보지 않는다.
1번과 4번의 내용이 서로 상충되지 않냐고 볼 수 있는데, SameSite 를 나누는 기준이 public suffix에 명시된 최상위 도메인과 관련이 있는데, 최상위 도메인이 명시되어 있으면, 그 앞의 부분까지 하나의 Site 라고 판단한다. 예를 들어, .com 최상위 도메인은 public suffix 에 명시되어 있으므로 abc.com 을 하나의 Site 라고 판단하고, abc.abc.com, def.abc.com 도 동일한 Site 라고 본다. 반면, .io 는 public suffix 에 명시되어 있지 않으므로 my.github.io 까지 하나의 Site 라고 본다. 즉 your.github.io 와 my.github.io 는 서로 다른 Site 라고 판단되어, SameSite 로 보지 않는다.
SameSite 속성별 특징
SameSite 속성에 값은 총 3가지 중 하나가 올 수 있는데, 각각의 특징을 알고 있어야 한다.
Strcit/ 쿠키를 오직 동일 Site 에서 설정한 경우에만 전송한다. 예를 들어,abc.com에서Set-Cookie로 설정한 쿠키는def.com로 전송하지 못하는 것이다.Lax/ 쿠키를 동일 Site 에서 설정한 경우에만 전송하지만, 조건을 만족하는 경우 다른 Site 여도 쿠키를 전송한다.fetchAPI를 사용한 요청일 경우<img>,<script>등의 태그를 사용한 리소스 요청인 경우 혹은<iframe>내부에서 네비게이션이 일어나는 경우
None/ 쿠키를 동일 Site, 다른 Site 구분 없이 항상 전송한다. 단,Secure라는 속성과 함께 설정되어야 한다. (https를 통한 요청에만 쿠키가 전송되며, localhost 는 예외처리 된다.)
Proxy
뜬금없이 무슨 Proxy냐고 하면.. 최근 회사에서 서비스 인증 방식을 바꾸는 작업에서 JWT를 사용하다 쿠키 방식으로 변경할 때에 서버와 도메인이 달라 쿠키 관련 이슈가 생겼었다. https 를 사용하지 않고 있어서 SameSite=None; 방식을 사용하기에는 무리가 있었고, 보안적으로 문제가 될 부분이라고 생각됬다.
배포 환경에서는 백엔드 서버말고 자기 자신을 호출하게끔 하여 /api 로 시작하는 요청에 대해 nginx 에서 proxy pass 하여 백엔드로 가게 해두어 동일 도메인으로 취급되는 것 같아 이슈가 재현이 되지 않는데, 개발 서버에서는 그 역할을 proxy 가 대신 해줄 수 있었다. 모두 proxy 를 사용하여 API 호출을 사용할 거라 생각해서 동일 도메인이라 쿠키가 당연히 전송될거라 생각했었는데, proxy 를 사용하지 않고 있었던 것이다..! 하여튼 proxy 설정을 통해 동일 도메인 취급을 받을 수 있었다.
인증 방식 자체가 JWT로 가는 경우가 많기도 하다보니, 쿠키 관련은 조금 생소한 느낌이 들기도 했다.
TypeScript erasableSyntaxOnly에 대한 학습과 정리
TypeScript erasableSyntaxOnly 옵션을 학습하고 정리해보기|7 months ago
TypeScript erasableSyntaxOnly에 대한 학습과 정리
타입스크립트 최근 버전에서 사용할 수 있는 erasableSyntaxOnly 옵션은 자바스크립트 런타임에서 지울 수 있는 타입스크립트 구문만 허용한다는 의미이다. 여기서 "지울 수 있는 타입스크립트 구문"에 대한 명칭이 다소 생소한데, 생각해보면 바로 알 수 있는 개념이다.
interface Student {
name: string
grade: string
score: number
}
const student: Student = {
name: 'Lee',
grade: 'A',
score: 96
}
예를 들어 위 타입스크립트 내용을 자바스크립트로 컴파일시 지울 수 있는 구문을 제외한 자바스크립트만 남는다.
const student = {
name: 'Lee',
grade: 'A',
score: 96
}
인터페이스 선언 내용은 지울 수 있는 타입스크립트 구문이기에 제거되고 실행 가능한 자바스크립트 구문만 남는다.
이러한 지울 수 있는 타입스크립트 구문은 타입, 인터페이스 등 컴파일 전 정적 타입 확인시에만 필요한 부분들은 타입스크립트 컴파일시 모두 제거된다. 런타임시에는 필요가 없는 부분이기 때문이다. 그렇다면 지울 수 없는 타입스크립트 구문에는 뭐가 있을까?
지울 수 없는 타입스크립트 구문
런타임시에도 영향을 끼치는, 즉 런타임에도 필요한 타입스크립트에서만 사용한 구문은 반드시 제거되지 않고 남게 된다.
enum HTTPMethod {
GET = 'get',
POST = 'post'
}
const method = HTTPMethod.GET
enum이 그 대표적인 예인데, 해당 타입스크립트 구문은 자바스크립트에 그대로 남아있게 된다.
var HTTPMethod
;(function (HTTPMethod) {
HTTPMethod['GET'] = 'get'
HTTPMethod['POST'] = 'post'
})(HTTPMethod || (HTTPMethod = {}))
const method = HTTPMethod.GET
자바스크립트로 컴파일되어도 enum이 남아있는 부분을 볼 수 있다.
function call() {
console.log('called');
}
namespace Http {
call();
export type Method = string;
}
---> 자바스크립트로 컴파일시
function call() {
console.log('called');
}
var Http;
(function (Http) {
call();
})(Http || (Http = {}));
namespace 선언도 마찬가지로, 자바스크립트로 컴파일되어도 남게된다.
이외에도 타입스크립트 클래스에서 생성자에 public 키워드를 달아 멤버를 초기화하는 shorthand 방식도 자바스크립트로 컴파일시 남아있기 되기 때문에 런타임에서 지울 수 없는 타입스크립트 구문이라고 볼 수 있다.
다시 돌아와서, --erasableSyntaxOnly 타입스크립트 옵션은 이러한 지울 수 없는 타입스크립트 구문을 허용하지 않는다는 의미이다. "지울 수 있는" 타입스크립트 구문만 허용한다. 타입스크립트 공식 문서에서는, Nodejs v23.6 부터 타입스크립트를 바로 실행할 수 잇도록 지원하기 시작한다는데, "지울 수 있는 타입스크립트 구문"을 포함한 타입스크립트 파일만 직접 실행할 수 있다고 이야기한다. 즉, 해당 옵션을 반드시 사용해야만 타입스크립트 파일을 직접 실행할 수 있는 것 같다.
Motion으로 splitText 구현하기
Motion 라이브러리를 이용해 splitText 애니메이션 구현하고 정리하기|9 months ago
splitText 애니메이션
framer motion에 대해서 아는 바는 없지만 이 라이브러리가 motion으로 이전된 것 같다. React/Vue용 라이브러리 및 범용 바닐라 자바스크립트를 지원한다. 여러 트랜지션 및 애니메이션에 대한 유틸리티/컴포넌트를 제공하고, CSS로 구현하기 귀찮은 여러 부분에 대해 알아서 처리해주는 유틸리티가 존재한다.
사용자의 제스쳐에 관련된 컨트롤도 가능하다는 것이 특장점 중 하나이다. 예를 들어, 사용자가 스크롤 할 때, 컨텐츠에 마우스를 올렸을 때, Viewport에 컨텐츠가 들어왔을 때 등 사용자와의 Interaction 부분도 다양하게 관리할 수 있다.
유틸리티 기능 중 splitText라는 기능이 있는데, 텍스트가 점진적으로 나타나는 효과를 주며 모던하고 깔끔한 느낌을 주는 유틸리티 기능이다. 해당 기능이 유료 버전에 한해서만 지원하는 것 같아, 직접 구현해보고 정리한다.
Idea
글자의 철자 분리를 통해 opacity 속성과 x, y 속성을 조작하면 쉽게 구현 가능해보였다.
function splitText(container, text) {}
철자 분리 함수의 시그니처는 이 정도, 글자들을 포함하고 있는 엘리먼트(container)와 텍스트 애니메이션 적용 대상인 텍스트를 인자로 받는다.
function splitText(container, text) {
const lines = text.split('\n')
const textEls = []
for (const line of lines) {
const lineEl = document.createElement('p')
let temp = ''
function push(pushBlank = false) {
const textEl = document.createElement('span')
textEl.textContent = temp
temp = ''
lineEl.appendChild(textEl)
textEls.push(textEl)
if (pushBlank) {
const blank = document.createTextNode(' ')
lineEl.appendChild(blank)
}
}
for (const char of line) {
if (char === ' ') {
push(true)
continue
}
temp += char
}
if (temp) push(false)
container.appendChild(lineEl)
}
return textEls
}
텍스트는 ABC ABC ABC\nABCD ABCDE ABC 형태로 받고, 줄바꿈 문자 단위로 라인별 문자열을 배열에 담고 있다가, 라인별로 단어 사이를 잘라 단어 단위로 애니메이션 적용을 하는 걸로 선회했다. (철자 단위 애니메이션은 뭔가 조잡하고 내용이 애니메이션에 치우친 느낌이라 전달하고자 하는 내용이 확실히 전달되지 못하는 느낌이 든다. 음... 내용을 깔끔하고 모던하게 전달하는게 아니라, 내가 만든 애니메이션을 한번 볼래? 라는 느낌?) 그리고 단어 단위 엘리먼트를 만들어 라인 단위 엘리먼트 하위에 넣는다. 이러면 단어 단위의 엘리먼트에 애니메이션을 적용하면 문서에 있는 애니메이션과 비슷한 느낌을 구현할 수 있을 것 같다.
const text =
'Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Vestibulum imperdiet mauris\nornare turpis semper, non egestas leo ultricies.\nPraesent sed laoreet ex. In eget orci arcu.'
const { animate, stagger } = Motion
function animateText() {
return animate(splitText(document.querySelector('.container'), text))
}
애니메이션 실행 함수까지 만들어주면 준비는 끝났다. stagger 메서드는 앞서 말했듯 애니메이션 대상이 복수개인 경우 상대적인 딜레이(앞 애니메이션이 실행되고 지연 시간)를 할당할 수 있게 해주는 유틸리티다. 이 기능이 해당 이펙트의 핵심 유틸리티다.
애니메이션 심화
inView 유틸리티는 HTML Intersection API를 활용해 애니메이션 대상이 viewport에 진입했는지 여부에 따라 애니메이션 실행 여부 결정을 관리할 수 있다. 제작한 애니메이션에 이 유틸리티를 적용해본다.
const { motion, stagger, inView } = Motion
inView(document.querySelector('.container'), el => {
animateText()
return leaving => {
animate(document.querySelector('.container'), { opacity: 0 })
}
})
첫번째 인자로 애니메이션 대상을 넘기고, 두번째 인자로 콜백 함수를 넘기면 엘리먼트가 viewport에 진입했을 때 애니메이션이 수행된다. 그리고, viewport에서 엘리먼트가 벗어나면 리턴한 콜백 함수가 실행된다. 즉, 여기서 viewport 벗어남 애니메이션을 수행하면 적절하다.
글씨 크기가 커서 작은 화면에서는 이상하게 보이는데, 큰 화면에서는 제대로 보인다. 얼추 예상한 것과 비슷하게 동작하므로 만족한다. 이 구현 과정을 통해 motion 라이브러리의 각각의 애니메이션 대상에 상대적인 딜레이를 넣을 수 있는 유틸리티인 stagger에 대해서 알게되었고, inView 메서드를 통해 Intersection API를 쉽게 활용할 수 있는 방법도 알게 되었다. 그리고 심화 과정을 통해, stagger의 딜레이를 조정하면 좀 더 다양한 애니메이션을 구현할 수 있는 걸 알게 되었다.
간단한 자바스크립트 배열 원소 정렬
자바스크립트 기본 제공 함수를 이용해 배열 내 원소를 정렬하는 방법|6 years ago
일반적인 배열 정렬
const arr = ['c', 'a', 'b', 1]
for (const i of arr) {
console.log(i.toString().charCodeAt())
}
// output: 99
// 97
// 98
// 49
문자열과 숫자로 이루어진 간단한 배열이 있다. 배열의 원소들을 모두 읽어들여 문자열로 변환한 뒤 해당 문자의 UTF-16 코드를 출력한다. Array.prototype.sort 함수는 매개변수로 정렬 방식을 정하지 않으면 기본적으로 이 코드를 기준으로 정렬한다.
const arr = ['c', 'a', 'b', 1, '나', '가', '다']
for (const i of arr) {
console.log(i.toString().charCodeAt())
}
arr.sort()
console.log(...arr)
/** result
* 99 (c)
* 97 (a)
* 98 (b)
* 49 (1)
* 45208 (나)
* 44032 (가)
* 45796 (다)
* 1 'a' 'b' 'c' '가' '나' '다'
**/
배열 원소에 숫자, 영어 알파벳, 한글이 있다. 각각 UTF-16 코드로 해당 숫자를 가지므로 이 숫자를 기준으로 정렬하게 되면 1, 'a', 'b', 'c', '가', '나', '다' 순이 된다. 이 함수는 기본적으로 이 함수를 호출하는 배열 자체를 바꾼다. 반환 값도 정렬 된 배열을 반환한다.
const numArr = [1, 100000, 99]
numArr.sort()
console.log(...numArr)
// 1 100000 99
문제점이 하나 존재한다. 배열 정렬 함수를 지정하지 않으면 배열의 모든 원소를 문자열로 바꿔 정렬을 시도한다. 그렇기 때문에 위의 배열에서 기대하는 값 1, 99, 100000은 출력되지 않는다.
정렬 함수 지정
위의 문제를 해결하기 위해서 정렬 함수를 지정한다. Array.prototype.sort 함수는 매개변수로 2개의 매개변수(여기서는 a,b)를 갖는 함수를 받으며 이 함수에서 양수를 반환하면 b가 더 낮은 값으로 정렬, 음수를 반환하면 a가 더 낮은 값으로 정렬 됨.
만약 이 함수가 0을 반환하면 a, b 두 값에 대해 변경하지 않고 다음 요소로 넘어감 이 정렬 함수 방식을 이용해서 위의 숫자 정렬을 다시 해보면 아래와 같이 된다.
const numArr = [1, 100000, 99]
numArr.sort((a, b) => (a > b ? 1 : -1))
console.log(...numArr)
// 1 99 100000
수가 오름차순으로 정렬된다. 반환하는 수를 거꾸로하면(부등호를 반대로 하면) 내림차순으로 정렬된다. 배열의 모든 원소가 사칙연산이 가능하다면 아래와 같은 방식도 가능하다.
const numArr = [1, 100000, 99]
numArr.sort((a, b) => a - b)
console.log(...numArr)
// 1 99 100000
반대로 더한 값을 반환하면 내림차순으로 정렬한다.
객체 정렬
배열 안의 모든 원소가 객체일 경우 객체의 어떤 값을 기준으로 정렬이 가능하다.
const personArr = [
{
name: 'Kim',
age: 30,
job: 'Teacher'
},
{
name: 'Lee',
age: 24,
job: 'Student'
},
{
name: 'Hwang',
age: 35,
job: 'Zookeeper'
}
]
위처럼 객체가 3개 들어있는 배열이 있다. 이 배열의 객체 값들 중 나이순으로 이들을 정렬하고 싶다고 하면 아래와 같이 a, b 객체의 age 속성을 서로 비교하면 된다.
personArr.sort((a, b) => a.age - b.age)
console.log(...personArr)
// { name: 'Lee', age: 24, job: 'Student' } { name: 'Kim', age: 30, job: 'Teacher' } { name: 'Hwang', age: 35, job: 'Zookeeper' }