ezgif.com-video-to-gif (1).gif

https://github.com/ohdal/Susim

수심

프로젝트 소개

⚙️ Development Environment

⚙️ Development List

⚙️ 사이트 소개

오프라인으로 진행한 관객 참여형 전시 **[수심(愁心)]**을 온라인으로 경험할 수 있습니다.

수심(愁心)

  1. 매우 근심함 또는 그런 마음
  2. anxiety, worry, apprehension

당신의 선택으로 만들어진 음악을 들려드립니다.

수심(불안)을 떠올리며 질문에 대한 답변을 해주세요. 답변에 따라 음악 리소스들이 합쳐져 사용자만의 음악을 만들고 들려드립니다.

음악을 들으며 당신의 “수심”을 작성해보세요.

수심을 배설하는동안 사용자의 마이크를 통해 사용자의 숨의기록(소리)을 받아와 시각화합니다. 시각화된 소리는 마치 물처럼 표현되고, 이는 곧 사용자의 수심의 시각화를 나타냅니다. 사용자가 작성하는 수심이 길어질수록 물의 높이가 높아지면서 사용자의 불안의 깊이를 표현합니다. 배설된 수심은 이내 다른 사용자의 수심과 합쳐지며 사라지게 됩니다.

“수심” 이미지를 이메일로 전송하세요.

시각화된 수심의 이미지를 이메일로 전송할 수 있습니다.

다른사람과 “수심”을 공유해보세요.

아카이브 페이지에서 다른사람의 수심을 살펴보세요. 다른 사용자와 수심을 공유하며 나의 수심을 해소할 수 있습니다.

프로젝트 주요 기능

프로젝트 주요 기능을 개발하면서 습득한 지식, 발생한 문제점과 해결방법을 정리합니다. (현재 정리중입니다 🤗)

Canvas API를 활용한 글자가 모래처럼 흩어지는 Mouse Interaction Effect 개발

DPR 기본개념

Animation Frame

문제점1. DPR 관련 처리

getImageData 메서드를 사용하여 데이터를 가져올 때도, DPR 관련 처리를 해주어야 합니다.

처음에 생각을 못해서 반토막난 높이와 너비의 ImageData를 반환받아 당황했던… 🥹

// met - cavnas에 그려진 text 너비와 높이 정보 (**{width: number; height: number}**)
// xPos, yPos - 사용자 화면 정가운데 position 값
const imgData = ctx.getImageData(
        (xPos - (met.width / 2) )* dpr,
        yPos * dpr,
        met.width * dpr,
        met.height * dpr
);

문제점2. getImageData 메서드에 대하여

인트로페이지에서 텍스트 클릭시 아래 이미지와 같은 버그가 발생했습니다.

MicrosoftTeams-image.png

getImageData메서드를 이용하여 가져온 데이터로부터 알파값을 비교해서 파티클을 생성해주는데, 위와같은 현상이 발생한다는건 알파값이 0이상이 들어온다고 판단하여 파티클 생성 조건문을 수정해주었습니다.

해당 현상은 특정 컴퓨터, 특정 계정에서만 발생하여 정확한 원인파악을 하지 못한점이 아쉬웠고, 조건문을 수정해줌으로써 버그는 해결하였습니다.

// 4의 배수의 값이 알파값 0 ~ 255
if (imgData.data[i + 3] > 125) {
	...
}

Audio, Canvas API를 활용한 사용자 오디오 데이터 시각화

문제점1. 화면 비율에 따른 오디오 데이터 시각화

수심은 1초에 3번씩 (3frame) 가져오는 사용자의 오디오 데이터와 랜덤한 값들로 이루어집니다. 랜덤한 값들은 수심을 꾸며주는 파티클의 x, y, opacity 값들을 의미합니다.

{
	...	
	data: '[[{"x":28.7109375,"y":561.2044270833334,"opacity":0.66}, ...]]'
}

x, y 값은 화면에 그려지는 파티클의 위치(좌표값)을 의미하는데, 문제는 값이 저장될 때의 컨버스크기와 그려질때의 컨버스크기가 다를 수 있다는 것 이었습니다.

  1. 사용자마다 기기화면이 다르다.
  2. 수심을 그리는 공통 컴포넌트인 LinearDataCanvas.tsx 가 아카이브 페이지에서도 사용된다. 아카이브 페이지에서는 대부분 값이 저장될 때의 컨버스크기보다 작은 CardComponent.tsx 내에서 수심이 그려진다.

따라서 값이 저장될 때 컨버스 widht, height 값을 같이 저장하여, 수심을 그릴 때 컨버스크기와의 비율을 구해 값을 곱해주어 처리했습니다.

// 비율 계산 함수
const getCanvasRatio = useCallback(
    ({ width, height }: canvasInfoType): { x: number; y: number } => {
      const ratio = { x: 1, y: 1 };
      if (canvas) {
        ratio.x = canvas.CANVAS_WIDTH / width;
        ratio.y = canvas.CANVAS_HEIGHT / height;
      }

      return ratio;
    },
    [canvas]
  );

// 컨버스에 파티클을 그리는 함수
const drawDot = useCallback(
    (arr: lineType, info: lineInfoType, ratio = { x: 1, y: 1 }, color = "#FFFFFF") => {
      if (!canvas) return;

      const { yPos } = info;
      const { r, g, b } = hexToRgb(color) || { r: 255, g: 255, b: 255 };
      const ctx = canvas.ctx as CanvasRenderingContext2D;

      if (arr) {
        for (let i = 0; i < arr.length; i += 1) {
          const { x, y, opacity } = arr[i];

          ctx.beginPath();
          ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${opacity as number})`;
					**// x, y 값에 비율 곱하기**
          ctx.arc((x + (yPos < 0 ? 10 : 0)) * ratio.x, (y - yPos) * ratio.y, 1, 0, (Math.PI / 180) * 360);
          ctx.fill();
          ctx.closePath();
        }
      }
    },
    [canvas]
  );

Firebase realtime-database를 사용한 serverless 프로젝트 구축

문제점1. 페이지네이션 처리

사이트 이용자들이 적은 수심을 보여줄 수 있는 아카이브 페이지에서 Infinity Scroll을 구현해야했고, 이를 위한 페이지네이션 처리가 필요하게 되었습니다.

Firebase에서 제공하는 realtime-database 데이터 필터링 메서드를 이용하여 원하는 데이터를 가져오기 위해서 기준점이 필요했고, Date값을 기준으로 잡았습니다.

문제점2. Date값을 기준으로 데이터를 불러오며

Date값을 기준으로 데이터를 불러오면서, 아주 적은 확률이지만 같은 시간, 분, 초에 데이터가 생성되는 경우를 처리해주어야 합니다.

Web Speech API를 활용한 TTS(Text to Speech)서비스 개발

문제점1. API 불안정성(cancel 메서드)

Web Speech APISpeechSynthesis API 라는 TTS 기능을 제공해주는 API가 있습니다. 해당 API는 speak, pause, resume, getVoices, cancle 5가지의 메서드를 제공합니다. 아래와 같이 utterance(발화) 인스턴스를 생성하여 원하는 텍스트를 발화할 수 있습니다.

let utterance = new SpeechSynthesisUtterance("안녕하세요. 윤현정입니다.");
speechSynthesis.speak(utterance);

speak 메서드를 호출하면 호출시 전달한 utterance발화큐에 쌓이게 되고 순서대로 발화하게 됩니다. 발화가 끝나면 발화큐에서 제거됩니다. cancel메서드는 발화큐에서 모든 utterance를 제거합니다. 다른 메서드의 상세 설명은 아래 MDN 문서를 참고해주세요.

SpeechSynthesis - Web APIs | MDN

문제는 cancel메서드 사용에서 발생됩니다. 저는 아래와 같은 로직을 원했습니다.

  1. 사용자의 마우스가 저장하기 버튼 위에 올라간다.
  2. “저장하기 버튼” 텍스트가 발화된다.
  3. 사용자가 발화 도중 저장하기 버튼을 클릭하게된다.
  4. “저장하기 버튼” 발화가 중단되고, 곧바로 “버튼 클릭” 발화가 시작된다.

보통은 사용자가 발화를 끝까지 다 들을거라고 생각하지만, 여러번 사이트를 이용하는 경우 등등 이미 들은 발화를 듣지 않고 넘기는 경우도 있을거라 생각하여 위와 같은 로직을 생각했습니다.

하지만 cancel메서드 호출 후에 speak메서드를 호출하면 제대로 utterance가 쌓이지 않고 에러를 뱉어냅니다. 해당 현상을 해결하기 위해 아래와 같은 방법을 생각하고 시도해 보았습니다.

결과적으론 위 세가지 방법으로도 해결할 수 없었고, cancel메서드를 사용하는건 위험하다고 판단하여 사용하지 않는 방향으로 로직을 변경하여 개발하였습니다.

  1. 사용자의 마우스가 저장하기 버튼 위에 올라간다.
  2. “저장하기 버튼” 텍스트가 발화된다.
  3. 저장하기 버튼 onClick 이벤트에 조건문을 추가하여 발화도중 또 다른 발화를 막는다.

문제점2. Web Speech API 기능을 제공하지 않는 브라우저 처리