텍스트(닉네임)에 따라 고유한 랜덤 색상 부여하기 Javascript
Posted 2019. 5. 4. 10:42, Filed under: 개발/JavaScript배경
트위치(Twitch.tv)의 채팅창을 비롯한 많은 채팅 시스템은 각 유저의 닉네임에 색깔을 입혀서 구별을 쉽게할 수 있도록 기능을 제공한다. 트위치의 경우, 채팅창에서의 자신의 닉네임 색상은 유저 자신이 직접 지정할 수도 있지만, 첫 접속 시에는 랜덤으로 지정되는 것으로 알고있다.
참고로 트위치에서 나의 채팅 닉네임 색상을 설정하는 방법은, 채팅 입력한 근처의 [톱니바퀴 모양 아이콘]을 누르고 [표시 방법 편집] 을 누르면 [이름 색상]을 설정할 수 있다.
- 어떤 유저가 채팅에 처음 접속할 때
- 사전에 정해둔 색상 배열에서 랜덤으로 색깔을 하나 골라
- 유저에게 지정하고
- 지정된 색깔을 서버에 저장해두고 활용하면 된다.
따라서 이 경우 색깔을 지정하는 방법을 별로 고민할 필요가 없다.
색상은 rgb 값을 랜덤으로 만들어낼 수도 있지만 일반적으로 사전에 정해진 몇 개의 색상에서 랜덤으로 가져오는데, 이유는 배경색에 따라 잘 보이는 색상이 정해져있기 때문이다. 물론 rgb 값 생성 시 범위 지정이나 밝음/어두움에 대한 평가가 가능하므로, 랜덤으로 색상을 생성하더라도 어떻게든 잘 보이게 만들어내는 것이 가능하긴 하다. 하지만 색상이 너무 많을 경우 난잡해보일 수도 있고, 정해진 리스트에서 색상을 가져다 쓰는게 그냥 간편하기 때문에 주로 이 방법이 사용되는 것같다.
내가 하고 싶었던 것
그런데 내가 하고 싶었던 것은, 어떤 채팅 시스템이 무작위 패턴의 익명 닉네임(무작위이지만 각 유저의 닉네임은 고유함)을 사용하고 있고, 닉네임을 색상으로 구분하지도 않을 때, 임의로 닉네임마다 랜덤 색상을 부여해서 유저 구분을 더 확실히 할 수 있는 브라우저 확장기능을 만드는 것이었다.
익명으로 채팅창을 유지하려는 운영자의 의도와는 다를 수 있지만, 특정 유저를 구분해서 기억하고자 한다기 보다는 채팅 흐름을 좀 더 쉽게 읽기 위한 의도가 더 컸다.
목표
즉 내가 원하는 것은 이렇다.
- 어떠한 텍스트(내 경우에는 닉네임)가 주어졌을 때 랜덤으로 색상이 결정되어야 하고,
- 주어진 텍스트에 따라 색상은 달라야 하지만,
- 동일한 텍스트에 의한 색상은 매번 같아야 한다.
동작 방법 구상
유저 정보를 클라이언트에 저장하는 경우의 동작 순서
- [유저 닉네임-색상] 리스트를 하나 만들어 클라이언트에 저장한다.
Cookie, Local Storage 나 확장기능에서 제공하는 저장공간을 이용할 수 있다. - 어떤 유저가 채팅을 쳤을 때, 리스트에 포함된 닉네임인지 확인한다.
- 만약 리스트에 포함되어 있지 않은 닉네임이라면
사전에 정해둔 색상 배열에서 랜덤으로 색깔을 하나 골라 유저에게 지정하고 저장한다. - 만약 리스트에 포함된 닉네임이라면 저장된 색상을 가져온다.
- 결정된 색상을 이용해 닉네임을 노출한다.
유저 정보를 클라이언트에 저장하지 않는 경우의 동작 순서
그렇지만 만약 유저 정보를 클라이언트에 저장하지 않기로 한다면, 아래와 같이 해야한다.
- 유저의 닉네임을 고유한 숫자값으로 변환한다.
- 해당 숫자값을 이용하여 사전에 정해둔 색상 배열에서 랜덤으로 색깔을 하나 골라
- 결정된 색상을 이용해 닉네임을 노출한다.
내 경우에는 대상 채팅 시스템의 사용자가 1000명~10000명 가까이 되기도 하고, 사용자가 유저 정보를 저장하게 하고 싶지도 않아서 후자의 방법을 사용하기로 했다.
코드
// Javascript implementation of Java’s String.hashCode() method // String to 32bit integer // https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ String.prototype.hashCode = function () { var hash = 0, i, char; if (this.length === 0) return hash; for (i = 0; i < this.length; i++) { char = this.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash & hash; // Convert to 32bit integer } return hash; }; var random_color = { lib: { orange:"#FF4500",brownishorange:"#DAA520",darkgreen:"#008000",blue:"#0000FF",blueviolet:"#8a2be2",brown:"#a52a2a",cadetblue:"#5f9ea0",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",crimson:"#dc143c",darkblue:"#00008b",darkgoldenrod:"#b8860b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",dimgray:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",forestgreen:"#228b22",grey:"#808080",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",lightcoral:"#f08080",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightslategrey:"#778899",limegreen:"#32cd32",magenta:"magenta",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",navy:"#000080",olive:"olive",olivedrab:"#6b8e23",orangered:"#ff4500",orchid:"#da70d6",pink:"#FF69B4",purple:"purple",red:"#FF0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",seagreen:"#2e8b57",sienna:"#a0522d",slateblue:"#6a5acd",slategrey:"#708090",steelblue:"#4682b4",tan:"#d2b48c",tomato:"#ff6347",violet:"#ee82ee", }, random: function (str) { var hash, color_key; var colors_keys = Object.keys(this.lib); var colors_keys_length = colors_keys.length; // 입력 값이 없는 경우 임의의 랜덤 색상값 출력 if (str === undefined) { hash = Math.floor((Math.random() * colors_keys_length) + 1); // random range: 0 - colors_keys_length } // 입력 값이 있는 경우, String 의 hash 에 따른 색상값 출력 else { hash = str.hashCode(); hash = ((hash % colors_keys_length) + colors_keys_length) % colors_keys_length; // range: 0 - colors_keys_length } color_key = colors_keys[hash]; return { name: color_key, rgb: this.lib[color_key] }; } };
lib 라는 객체에 색상을 저장해놨는데, 흰색 배경에서 잘 보이는 글자색을 내가 임의로 고른 것이다. 만약 이 객체에 색상을 추가하거나 제거하여 배열 길이가 바뀌는 경우, 같은 텍스트에 대해 변경 이전과 이후의 색깔이 달라질 수 있다.
텍스트의 고유한 숫자값, 그러니까 해쉬값을 계산하는 방법은 내가 가져다 쓴 방법 외에도 아주 많았다. 내가 가져다 쓴 방법의 경우 << 와 & 와 같은 비트연산자를 사용하는데, 비트연산자를 사용하지 않고도 동일한 계산이 가능하지만 비트연산자를 이용한 계산이 더 빠르다고 한다. 최종적으로 32bit 의 integer 값이 계산된다. 비트 연산자에 대한 설명은 이곳을 참고.
이렇게 계산한 해쉬값의 경우 마이너스 값을 가질 수도 있기 때문에, 해쉬값 계산 후 이 값을 전체 색상의 개수로 나눠 나머지를 가져올 때
그냥 hash % colors_keys_length 와 같이 계산하면 이 값이 마이너스 값을 가질 수도 있기 때문에 주의해야 한다.
채팅방 접속 인원보다 실제 채팅을 하는 인원은 상대적으로 소수이므로, 계산된 색상값을 javascript 변수 값에 일정 개수만큼만 저장해둬서 계산을 줄일 수도 있을 것 같은데, 배열에 저장하고 저장된 값을 찾아다 쓰는것과 매번 계산하는 것이 얼마만큼의 차이를 보일지는 모르겠다. 애초에 의미없는 수준일듯.
사용 예제는 아래와 같다. 버튼을 클릭하면 각 element의 html() 값을 이용하여 색상을 지정하도록 했다. 동일한 내용을 가지는 경우 항상 동일한 색상으로 표현되는 것을 볼 수 있다. 링크는 https://jsfiddle.net/nomomo/9j21h7ba/