컴포넌트/아바타, 페르소나
AvatarGroup (아바타그룹)
VanillaFront
2026. 9. 20. 16:21
Va.AvatarGroup — 아바타 그룹 컨테이너
여러 아바타를 나란히 또는 겹쳐 표시하고, 지정 개수 초과 시 자동으로 +N 팝업으로 접어주는 컨테이너입니다. 협업 UI에서 프로젝트 참여자, 채팅 참가자, 팀 멤버 목록 등을 콤팩트하게 표시할 때 사용됩니다.

클래스 정보
Va.AvatarGroup extends Va.Component
tagName: 'avatarGroup' (실제 렌더링은 <div>)
isContainer: true
파일: va_avatar.js (Va.Avatar와 같은 모듈)
기본 사용법
{
tagName: 'avatarGroup',
stackSize: 4,
layout: 'stack',
tags: [
{ tagName: 'avatar', image: user1.photo },
{ tagName: 'avatar', image: user2.photo },
{ tagName: 'avatar', image: user3.photo },
{ tagName: 'avatar', image: user4.photo },
{ tagName: 'avatar', image: user5.photo }, // 5명째부터 '+N' 팝업
{ tagName: 'avatar', image: user6.photo }
]
}
또는 코드로:
let group = new Va.AvatarGroup({ stackSize: 4, layout: 'stack' });
users.forEach(user => {
group.append(new Va.Avatar({ image: user.photo }));
});
parent.append(group);
핵심 동작 — 자동 오버플로우
stackSize를 초과하는 아바타는 자동으로 팝업으로 이동:
[👤][👤][👤][👤][+3] ← stackSize: 4, 총 7명일 때
↓ 클릭
+3 팝업:
[👤]
[👤]
[👤]
- 처음 stackSize(기본 4)개는 화면에 나란히 표시
- 나머지는 "+N" 오버플로우 아바타로 접힘
- "+N" 클릭 → 팝오버로 나머지 표시
- 화면 밖 클릭 시 팝오버 자동 닫힘
속성
속성타입기본값설명
| stackSize | Number | 4 | 화면에 표시할 최대 개수 (초과분은 팝업으로) |
| layout | String | 'spread' | 'spread'(펼침) 또는 'stack'(겹침) |
| popWidth | Number | — | 오버플로우 팝업 너비 |
| gap | String | — | 아바타 사이 간격 |
| stopPropagation | Boolean | true | 이벤트 전파 차단 |
| size | String | — | 자식 아바타에 자동 전파되는 크기 |
layout 두 가지 모드
'spread' (기본) — 나란히 배치
[👤] [👤] [👤] [👤] [+3]
- 아바타끼리 간격을 두고 나란히
- 이름이 옆에 붙는 리스트에 적합
'stack' — 겹치게 배치
[👤][👤][👤][👤][+3]
(뒷 것이 앞을 살짝 덮음)
- 아바타끼리 겹쳐서 콤팩트한 표시
- 좁은 공간에 여러 명 표시 (참여자 요약 등)
- Slack, Notion 같은 협업 툴의 표준 패턴
이벤트
이벤트발생 시점콜백 인자
| expand | "+N" 팝업 열림 | (sender, element, evt) |
| collapse | "+N" 팝업 닫힘 | (sender, element, evt) |
각 자식 아바타의 이벤트는 아바타 자체에 개별적으로 바인딩합니다.
메서드
- append(avatar) — 아바타 추가 (초과 시 자동으로 팝업으로)
- add(avatar) — append의 별칭
- arrange() — 화면/팝업 사이 아바타 재정렬 (제거 후 균형 맞춤)
- removeKey(key) — key로 아바타 찾아 제거 후 자동 arrange
내부 구조
<div class="va-avatar-group stack">
<span class="content" elname="content">
<!-- 처음 stackSize개 아바타 -->
<span class="va-avatar circular">...</span>
<span class="va-avatar circular">...</span>
...
</span>
<span class="va-avatar" cpname="avatarPopOver">
<!-- "+3" 오버플로우 트리거 -->
</span>
<div elname="pop" class="va-avatar-group-pop" style="display:none; position:absolute">
<!-- 초과 아바타들 (팝업 열릴 때만 표시) -->
</div>
</div>
사용 시점 — 언제 쓰나
- 프로젝트 참여자 표시 — 협업 툴, 이슈 트래커
- 채팅방 참가자 — 그룹 채팅 헤더
- 팀 멤버 요약 — 팀/부서 카드
- 문서 편집자 목록 — 실시간 협업 문서
- 최근 방문자 — 페이지 조회자 표시
사용하지 말아야 할 때
- 단일 사용자 표시 → Va.Avatar 단독 사용
- 아바타 + 이름/역할 조합 리스트 → Va.Persona 반복
- 정형적 사용자 리스트 (테이블) → Va.Grid
자주 쓰는 조합 예시
협업 프로젝트 참여자 (겹침 배치)
{
tagName: 'avatarGroup',
stackSize: 5,
layout: 'stack',
size: 'small',
tags: project.members.map(member => ({
tagName: 'avatar',
image: member.photo,
initial: member.initial,
backgroundColor: member.color
}))
}
채팅방 헤더
{
tagName: 'div',
layout: 'ds-flex fd-row ai-center gap-m',
tags: [{
tagName: 'avatarGroup',
stackSize: 3,
layout: 'stack',
size: 'small',
tags: room.participants.map(p => ({
tagName: 'avatar',
image: p.photo
}))
},{
tagName: 'div',
tags: [
{ tagName: 'title', innerHTML: room.name },
{ tagName: 'subTitle', innerHTML: `${room.participants.length}명 참여` }
]
}]
}
팀 카드
{
tagName: 'div',
style: { padding: '15px', border: '1px solid var(--colorNeutralStroke)' },
tags: [
{ tagName: 'title', innerHTML: '개발팀' },
{ tagName: 'subTitle', innerHTML: `${team.length}명` },
{
tagName: 'avatarGroup',
stackSize: 6,
layout: 'stack',
size: 'medium',
style: { marginTop: '10px' },
tags: team.map(member => ({
tagName: 'avatar',
image: member.photo,
initial: member.name.substring(0, 1),
onClick: 'onClickMember'
}))
}
]
}
동적으로 참여자 추가/제거
config(){
return {
ref: 'refGroup',
tagName: 'avatarGroup',
stackSize: 4,
layout: 'stack'
};
}
addUser(user){
let avatar = new Va.Avatar({
key: user.id,
image: user.photo
});
this.getRef('refGroup').append(avatar);
}
removeUser(userId){
this.getRef('refGroup').removeKey(userId);
}
아바타 클릭 이벤트
{
tagName: 'avatarGroup',
stackSize: 4,
layout: 'stack',
onExpand: 'onExpandGroup',
onCollapse: 'onCollapseGroup',
tags: users.map(u => ({
tagName: 'avatar',
image: u.photo,
key: u.id,
onClick: 'onClickAvatar'
}))
}
// View 안:
onClickAvatar(sender){
console.log('아바타 클릭:', sender.key);
this.showUserProfile(sender.key);
}
onExpandGroup(sender){
console.log('오버플로우 팝업 열림');
}
실전 예시 — 이슈 트래커 카드
config(){
return {
tagName: 'section',
layout: 'ds-flex fd-column gap-s',
tags: this.issues.map(issue => ({
tagName: 'div',
layout: 'ds-flex fd-row ai-center gap-m',
style: {
padding: '15px',
border: '1px solid var(--colorNeutralStroke)',
borderRadius: 'var(--sizeRadiusM)'
},
tags: [{
tagName: 'div',
style: { flex: 1 },
tags: [
{ tagName: 'title', innerHTML: issue.title },
{ tagName: 'subTitle', innerHTML: `#${issue.id} · ${issue.status}` }
]
},{
tagName: 'avatarGroup',
stackSize: 3,
layout: 'stack',
size: 'small',
tags: issue.assignees.map(user => ({
tagName: 'avatar',
image: user.photo,
initial: user.initial,
key: user.id
}))
}]
}))
};
}
주의사항
- 자식은 반드시 Va.Avatar — 다른 컴포넌트를 넣으면 팝업/카운트 로직이 오작동
- stackSize가 클수록 화면 폭 차지 — 반응형 UI에선 화면 크기별로 stackSize 조정 필요
- layout: 'stack'의 겹침 마진은 CSS 통제 — 각 아바타에 style로 marginLeft override 시 룩 어긋날 수 있음
- key 지정 권장 — removeKey()로 특정 아바타 제거하려면 각 아바타에 key 필수
- 팝업은 화면 위쪽에 나타남 — 아바타가 화면 상단에 붙어 있으면 잘림 가능성 있음
- size 속성은 자식에 자동 전파 — 개별 아바타의 size를 명시하지 않은 경우만
- 팝업 안 아바타도 크기/스타일 상속 — 별도 처리 불필요
- arrange() 자동 호출 시점 — removeKey() 후는 자동. append()는 자동 배치되지만, DOM 직접 조작 후엔 수동 arrange() 필요
- autoHide 로직 사용 — 팝업 열려있을 때 다른 곳 클릭 시 자동 닫힘 (Va.autoHideEl 시스템 활용)
AvatarGroup vs Avatar 반복
AvatarGroup 사용:
- 5명 이상 표시 시 오버플로우 처리 자동
- Slack/Notion 스타일 겹침 배치 기본 지원
- 팝업 로직 내장
Avatar 반복 (Div + Avatar들):
- 완전 커스터마이징 필요할 때
- 각 아바타에 이름 표시 등 추가 정보 있을 때 → Va.Persona 반복이 더 나음
대안 비교
상황추천
| 여러 사용자 콤팩트 표시 | Va.AvatarGroup |
| 개별 사용자 표시 | Va.Avatar |
| 아바타 + 이름/역할 리스트 | Va.Persona 반복 |
| 정형 사용자 테이블 | Va.Grid |
| 태그/뱃지 그룹 | Va.Tag 반복 |
참고
- API 문서 페이지: https://vanillafront.com/docs.html?theme=light#main#apiavatargroup
- 연관: Va.Avatar(자식 컴포넌트), Va.Persona(이름·역할 조합 대안)