junyeokk
Blog
react·2024. 11. 21

avvvatars-react

사용자 프로필 이미지가 없을 때, 뭘 보여줄 것인가? 가장 단순한 방법은 회색 기본 아이콘을 넣는 거다. 근데 이게 여러 명이 모이면 전부 똑같은 회색 원이 나열되면서 누가 누군지 구분이 안 된다. 채팅 화면에서 이러면 UX가 상당히 나빠진다.

그래서 보통 두 가지 접근을 쓴다.

  1. Gravatar 같은 외부 서비스 — 이메일 해시로 글로벌 아바타를 가져온다. 근데 사용자가 Gravatar에 등록하지 않으면 결국 기본 이미지가 나온다.
  2. 해시 기반 자동 생성 — 사용자 식별자(이름, 이메일 등)를 해시해서 고유한 시각적 아바타를 생성한다. 등록 같은 건 필요 없고, 같은 입력이면 항상 같은 결과가 나온다.

avvvatars-react는 두 번째 접근이다. 문자열 하나를 주면 그 문자열에 고유한 색상 + 도형(또는 이니셜) 조합의 아바타를 생성한다. 20KB 미만으로 가볍고, 별도의 API 호출이나 이미지 파일이 필요 없다.


동작 원리

avvvatars의 핵심은 결정론적 해싱이다. 내부적으로 입력 문자열을 해시 함수에 넣어서 숫자로 변환하고, 이 숫자를 이용해 다음을 결정한다:

  • 색상: 40개의 미리 정의된 컬러 팔레트 중 하나
  • 도형: 60개의 미리 디자인된 SVG 도형 중 하나
  • 이니셜: 입력 문자열의 앞 2글자

같은 문자열을 넣으면 해시값이 같으므로, 항상 동일한 아바타가 나온다. user123을 전달하면 어제도, 오늘도, 내일도 똑같은 파란색 삼각형(예시)이 렌더링된다. 이게 핵심이다 — 상태 저장 없이 일관된 시각적 식별자를 만들 수 있다.

일반적인 해시 기반 아바타 라이브러리(예: identicon)는 패턴이 기계적이고 딱딱한 느낌인데, avvvatars는 디자이너가 직접 만든 도형과 색상 조합을 사용하기 때문에 시각적으로 부드럽다.


기본 사용법

tsx
import Avvvatars from 'avvvatars-react';

function UserProfile() {
  return <Avvvatars value="user@example.com" />;
}

value만 넘기면 동작한다. 이메일이든, 유저 ID든, 닉네임이든 아무 문자열이나 된다. 중요한 건 같은 사용자에게 항상 같은 문자열을 넘겨야 일관된 아바타가 나온다는 점이다.


Props 상세

style: 'character' | 'shape'

아바타의 렌더링 스타일을 결정한다. 기본값은 character.

tsx
// 이니셜(텍스트) 스타일 - 기본
<Avvvatars value="john_doe" style="character" />

// 도형 스타일
<Avvvatars value="john_doe" style="shape" />

character는 입력값의 앞 2글자를 대문자로 표시한다. john_doe를 넣으면 "JO"가 보인다. shape는 텍스트 대신 해시값에 매핑된 SVG 도형을 보여준다.

용도에 따라 선택하면 된다:

  • 이름이 의미 있는 경우 (실명 사용자) → character가 직관적
  • 식별자가 무의미한 경우 (익명 채팅, 해시 ID) → shape가 나음

displayValue: string

character 스타일에서 표시할 텍스트를 오버라이드한다.

tsx
// value의 앞 2글자 "US" 대신 "JD"를 표시
<Avvvatars value="user@example.com" displayValue="JD" />

value는 해시 계산용으로만 쓰이고, 화면에 보이는 텍스트만 바꾼다. 색상과 도형은 여전히 value 기준이다.

size: number

아바타의 크기를 px 단위로 지정한다. 기본값은 32.

tsx
<Avvvatars value="user@example.com" size={48} />

shadow: boolean

아바타 주위에 그림자를 추가한다. 기본값은 false.

tsx
<Avvvatars value="user@example.com" shadow={true} />

밝은 배경에서 아바타를 살짝 띄워주는 효과가 있다. 카드 위에 올려놓을 때 유용하다.

radius: number

아바타의 border-radius를 지정한다. 기본값은 size와 동일해서 완전한 원이 된다.

tsx
// 둥근 사각형
<Avvvatars value="user@example.com" size={48} radius={10} />

// 완전한 원 (기본)
<Avvvatars value="user@example.com" size={48} />

border / borderSize / borderColor

테두리 관련 옵션들이다.

tsx
<Avvvatars 
  value="user@example.com" 
  border={true}
  borderSize={2}
  borderColor="#e0e0e0"
/>

여러 아바타를 겹쳐서 보여주는 아바타 스택(avatar group)에서 테두리가 유용하다. 흰 배경과 구분해주는 역할도 한다.


대안 라이브러리 비교

아바타 자동 생성 라이브러리는 여러 개가 있다. 각각 장단점이 다르다.

boring-avatars

tsx
import Avatar from 'boring-avatars';

<Avatar name="user@example.com" variant="beam" colors={['#264653', '#2a9d8f']} />

boring-avatars는 5가지 variant(beam, marble, pixel, sunset, ring)를 제공한다. avvvatars보다 시각적 다양성이 높지만, 색상 팔레트를 직접 지정해야 하는 경우가 많다. avvvatars는 미리 최적화된 40개 색상이 내장되어 있어서 설정 없이도 보기 좋다.

react-avatar

tsx
import Avatar from 'react-avatar';

<Avatar name="John Doe" src="https://example.com/avatar.jpg" />

react-avatar는 Gravatar, Facebook, Google 등 외부 소스에서 이미지를 가져오는 fallback 체인을 지원한다. 실제 프로필 사진이 있을 때 더 적합하고, 자동 생성 아바타는 최후의 수단이다. avvvatars는 반대로 자동 생성 자체가 목적이다.

@dicebear/core

ts
import { createAvatar } from '@dicebear/core';
import { lorelei } from '@dicebear/collection';

const avatar = createAvatar(lorelei, { seed: 'user@example.com' });
const svg = avatar.toString();

DiceBear는 수십 가지 아바타 스타일을 제공하는 가장 풍부한 옵션이다. 다만 패키지 크기가 크고(사용하는 스타일에 따라 다름), React 전용이 아니라 프레임워크 독립적이다. 커스터마이징이 많이 필요한 경우에 적합하다.

정리

라이브러리크기스타일 수설정 복잡도외부 이미지
avvvatars-react~20KB2 (character/shape)낮음미지원
boring-avatars~6KB5 variants중간미지원
react-avatar~30KBN/A높음지원
@dicebear스타일별 상이30+높음미지원

avvvatars의 포지션은 명확하다: 설정 최소화 + 깔끔한 기본 디자인. "아바타가 필요한데 커스터마이징은 귀찮다"는 상황에서 가장 빠른 선택이다.


실전 활용 패턴

익명 채팅에서 사용자 구분

프로필 이미지 없는 익명 환경에서 아바타를 생성하는 전형적인 사례다.

tsx
interface ChatMessage {
  nickname: string;
  content: string;
  timestamp: Date;
}

function ChatItem({ nickname, content, timestamp }: ChatMessage) {
  return (
    <div style={{ display: 'flex', gap: '12px', alignItems: 'flex-start' }}>
      <Avvvatars value={nickname} size={40} />
      <div>
        <span style={{ fontWeight: 'bold' }}>{nickname}</span>
        <p>{content}</p>
        <time>{timestamp.toLocaleTimeString()}</time>
      </div>
    </div>
  );
}

같은 닉네임이면 항상 같은 아바타가 나오니까, 스크롤하면서 "아 이 사람이 아까 그 사람이구나"를 시각적으로 인식할 수 있다.

아바타 + 실제 이미지 Fallback

실제 프로필 이미지가 있을 때는 그걸 보여주고, 없을 때만 avvvatars를 사용하는 패턴이다.

tsx
interface UserAvatarProps {
  userId: string;
  profileImage?: string | null;
  size?: number;
}

function UserAvatar({ userId, profileImage, size = 40 }: UserAvatarProps) {
  if (profileImage) {
    return (
      <img
        src={profileImage}
        alt="avatar"
        width={size}
        height={size}
        style={{ borderRadius: '50%', objectFit: 'cover' }}
      />
    );
  }
  
  return <Avvvatars value={userId} size={size} />;
}

아바타 그룹 (겹치는 아바타)

참여자 목록을 아바타 스택으로 보여줄 때:

tsx
function AvatarGroup({ users, max = 5 }: { users: string[]; max?: number }) {
  const visible = users.slice(0, max);
  const remaining = users.length - max;

  return (
    <div style={{ display: 'flex' }}>
      {visible.map((user, i) => (
        <div
          key={user}
          style={{
            marginLeft: i > 0 ? '-8px' : 0,
            position: 'relative',
            zIndex: visible.length - i,
          }}
        >
          <Avvvatars value={user} size={32} border={true} borderColor="#fff" />
        </div>
      ))}
      {remaining > 0 && (
        <div
          style={{
            marginLeft: '-8px',
            width: 32,
            height: 32,
            borderRadius: '50%',
            background: '#e0e0e0',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            fontSize: 12,
          }}
        >
          +{remaining}
        </div>
      )}
    </div>
  );
}

border를 켜면 아바타끼리 겹칠 때 경계가 명확해진다.


내부 구현 이해하기

avvvatars가 내부적으로 하는 일을 간단히 요약하면:

  1. 입력 문자열 → 숫자 변환: 문자열의 각 문자 코드를 합산하거나 해싱해서 하나의 정수를 만든다
  2. 색상 인덱스: hash % 40으로 40개 색상 중 하나를 선택
  3. 도형 인덱스: hash % 60으로 60개 도형 중 하나를 선택 (shape 모드)
  4. SVG 렌더링: 선택된 색상과 도형으로 인라인 SVG를 생성

이런 해시 기반 결정론적 생성의 핵심 특성:

  • 동일 입력 → 동일 출력: DB에 아바타를 저장할 필요가 없다
  • 서버 불필요: 모든 계산이 클라이언트에서 일어난다
  • 충돌 가능성: 40 × 60 = 2,400 가지 조합이므로, 사용자가 많으면 같은 아바타가 나올 수 있다. 하지만 채팅방 같은 소규모 컨텍스트에서는 충분하다.

직접 비슷한 걸 만든다면:

ts
function simpleHash(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = ((hash << 5) - hash) + str.charCodeAt(i);
    hash |= 0; // 32비트 정수로 변환
  }
  return Math.abs(hash);
}

const COLORS = ['#FF6B6B', '#4ECDC4', '#45B7D1', /* ... */];
const color = COLORS[simpleHash('user@example.com') % COLORS.length];

이런 식으로 해시값을 이용해 배열 인덱스를 결정하는 패턴이다. avvvatars는 이걸 예쁜 SVG 도형과 잘 짜인 색상 팔레트로 포장한 것이다.


주의점

  • value가 바뀌면 아바타도 바뀐다: 닉네임 변경 같은 상황에서 이전 아바타와 달라질 수 있다. 일관성이 필요하면 변하지 않는 값(UUID 등)을 넘기는 게 낫다.
  • SSR 호환: SVG를 직접 렌더링하므로 서버 사이드 렌더링에서도 문제 없다. 이미지 URL 기반 라이브러리와 달리 별도의 HTTP 요청이 발생하지 않는다.
  • 접근성: alt 텍스트를 별도로 제공하는 prop이 없으므로, 필요하면 감싸는 요소에 aria-label을 추가하는 게 좋다.

정리

  • 해시 기반 결정론적 생성: 같은 문자열 → 같은 아바타. 상태 저장이나 API 호출 없이 클라이언트에서 즉시 렌더링
  • 설정 최소화: value prop 하나만 넘기면 동작. 40개 색상 × 60개 도형이 내장되어 있어서 별도 커스터마이징 없이도 보기 좋음
  • SSR 호환 + 경량: 인라인 SVG 렌더링이라 서버 사이드에서도 문제 없고, 20KB 미만으로 번들 부담이 적음

관련 문서