본문 바로가기

WEB

못말리는 Input 달래주기 2편 (이모지 입력)

안녕하세요 WEB 김채현입니다!

 

BEAT는 대학생 공연 동아리들이 타겟층이기 때문에 공연명, 소개, 유의사항 등에 이모지가 들어갈 수 있다고 생각했습니다.

그런데 이모지를 입력하면 이슈가 발생했는데요...🥹🥹

 

⚠️ 이모지는 한 글자가 아니다.

 

글자수가 5로 되어야 하는데 이상하게 이모지마다 글자수가 다르고 심지어 이모지 하나에 11을 차지하기도 했습니다.

 

이유가 무엇일까요?

이모지는 유니코드에 포함되어 있습니다. 유니코드 평면 중 다국어 보충 평면(SMP)에 포함되어 있는데

이 평면에서 U+1F000 ~ U+1F9FF 영역에 이모지가 위치해있습니다.

하지만 모든 이모지가 이 유니코드 평면에 존재하지는 않습니다.

이모지에는 여러 유니코드 문자가 결합되어 하나의 이모지를 표현하는 이모지 시퀀스 (Emoji Sequence)라는 것이 존재하기 때문입니다.

  • 🐻 (Bear) + ZWJ + ❄️ (Snowflake) = 🐻‍❄️ (Polar Bear)
  • 👨 (Man) + ZWJ + 🎤 (Microphone) = 👨‍🎤 (Man Singer)
  • ❤️ (Heart) + ZWJ + 🔥 (Fire) = ❤️‍🔥 (Heart on Fire)
  • 👁 (Eye) + ZWJ + 🗨 (Left Speech Bubble) = 👁‍🗨 (Eye in Speech Bubble)
  • 👨 (Man) + ZWJ + 👩 (Woman) + ZWJ + 👧 (Girl) + ZWJ + 👦 (Boy) = 👨‍👩‍👧‍👦 (Family: Man, Woman, Girl, Boy)

따라서 우리 눈에는 한 글자로 보이지만 따지고 보면 한 글자가 아닌 이모지들이 있어서 글자수가 예상과 다르게 나오게 되는 것 입니다.

 

💡 Intl.Segmenter

하나의 이모지를 하나로 세고 싶다면 Intl.Segmenter을 사용하면 됩니다!

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmente

 

Intl.Segmenter - JavaScript | MDN

The Intl.Segmenter object enables locale-sensitive text segmentation, enabling you to get meaningful items (graphemes, words or sentences) from a string.

developer.mozilla.org

// 이모지 및 이모지 조합을 올바르게 인식하는 함수
export const splitGraphemes = (str: string): string[] => {
  const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
  const segments = segmenter.segment(str);
  return Array.from(segments, (segment) => segment.segment);
};
<S.TextCap>{`${splitGraphemes(value as string).length}/${maxLength}`}</S.TextCap>

한 이모지가 하나의 글자로 처리가 잘되는 것을 볼 수 있습니다!!

 

만약 타입스크립트가 Intl을 인식하지 못한다면??

`global.d.ts`에 아래와 같이 작성하면 잘 작동하게 됩니다.

declare namespace Intl {
  class Segmenter {
    constructor(locales?: string | string[], options?: SegmenterOptions);
    segment(input: string): Iterable<SegmentData>;

    static supportedLocalesOf(locales: string | string[], options?: SegmenterOptions): string[];
  }

  interface SegmenterOptions {
    granularity?: "grapheme" | "word" | "sentence";
  }

  interface SegmentData {
    segment: string;
    index: number;
    input: string;
    isWordLike?: boolean;
  }
}

그런데 글자 수 제한에 막히게 되었습니다.

maxLength로 속성을 주면 이모지를 하나로 인식하지 않기 때문에 Input에 maxLength 속성을 주지 않고

if (maxLength && splitGraphemes(filteredValue as string).length > maxLength) {
          filteredValue = splitGraphemes(filteredValue as string)
            .slice(0, maxLength)
            .join("");
 }

직접 maxLength만큼 배열을 잘라서 합쳐주면 원하는대로 maxLength가 작동됩니다.

프로필 이미지

WEB_김채현

공연 등록하기를 맡은 FE 개발자 김채현입니다.