https://github.com/ohdal/Susim
HTML/CSS/JS
Typescript
React
vite
tailwindCSS
styled-components
Firebase realtimeDB
emailJS
VScode
오프라인으로 진행한 관객 참여형 전시 **[수심(愁心)]**을 온라인으로 경험할 수 있습니다.
수심(愁心)
당신의 선택으로 만들어진 음악을 들려드립니다.
수심(불안)을 떠올리며 질문에 대한 답변을 해주세요. 답변에 따라 음악 리소스들이 합쳐져 사용자만의 음악을 만들고 들려드립니다.
음악을 들으며 당신의 “수심”을 작성해보세요.
수심을 배설하는동안 사용자의 마이크를 통해 사용자의 숨의기록(소리)을 받아와 시각화합니다. 시각화된 소리는 마치 물처럼 표현되고, 이는 곧 사용자의 수심의 시각화를 나타냅니다. 사용자가 작성하는 수심이 길어질수록 물의 높이가 높아지면서 사용자의 불안의 깊이를 표현합니다. 배설된 수심은 이내 다른 사용자의 수심과 합쳐지며 사라지게 됩니다.
“수심” 이미지를 이메일로 전송하세요.
시각화된 수심의 이미지를 이메일로 전송할 수 있습니다.
다른사람과 “수심”을 공유해보세요.
아카이브 페이지에서 다른사람의 수심을 살펴보세요. 다른 사용자와 수심을 공유하며 나의 수심을 해소할 수 있습니다.
프로젝트 주요 기능을 개발하면서 습득한 지식, 발생한 문제점과 해결방법을 정리합니다. (현재 정리중입니다 🤗)
문제점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 메서드에 대하여
인트로페이지에서 텍스트 클릭시 아래 이미지와 같은 버그가 발생했습니다.
getImageData
메서드를 이용하여 가져온 데이터로부터 알파값을 비교해서 파티클을 생성해주는데, 위와같은 현상이 발생한다는건 알파값이 0이상이 들어온다고 판단하여 파티클 생성 조건문을 수정해주었습니다.
해당 현상은 특정 컴퓨터, 특정 계정에서만 발생하여 정확한 원인파악을 하지 못한점이 아쉬웠고, 조건문을 수정해줌으로써 버그는 해결하였습니다.
// 4의 배수의 값이 알파값 0 ~ 255
if (imgData.data[i + 3] > 125) {
...
}
문제점1. 화면 비율에 따른 오디오 데이터 시각화
수심은 1초에 3번씩 (3frame) 가져오는 사용자의 오디오 데이터와 랜덤한 값들로 이루어집니다. 랜덤한 값들은 수심을 꾸며주는 파티클의 x
, y
, opacity
값들을 의미합니다.
{
...
data: '[[{"x":28.7109375,"y":561.2044270833334,"opacity":0.66}, ...]]'
}
x
, y
값은 화면에 그려지는 파티클의 위치(좌표값)을 의미하는데, 문제는 값이 저장될 때의 컨버스크기와 그려질때의 컨버스크기가 다를 수 있다는 것 이었습니다.
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]
);
문제점1. 페이지네이션 처리
사이트 이용자들이 적은 수심을 보여줄 수 있는 아카이브 페이지에서 Infinity Scroll
을 구현해야했고, 이를 위한 페이지네이션 처리가 필요하게 되었습니다.
Firebase에서 제공하는 realtime-database 데이터 필터링 메서드를 이용하여 원하는 데이터를 가져오기 위해서 기준점이 필요했고, Date값을 기준으로 잡았습니다.
문제점2. Date값을 기준으로 데이터를 불러오며
Date값을 기준으로 데이터를 불러오면서, 아주 적은 확률이지만 같은 시간, 분, 초에 데이터가 생성되는 경우를 처리해주어야 합니다.
문제점1. API 불안정성(cancel 메서드)
Web Speech API
중 SpeechSynthesis 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
메서드 사용에서 발생됩니다. 저는 아래와 같은 로직을 원했습니다.
보통은 사용자가 발화를 끝까지 다 들을거라고 생각하지만, 여러번 사이트를 이용하는 경우 등등 이미 들은 발화를 듣지 않고 넘기는 경우도 있을거라 생각하여 위와 같은 로직을 생각했습니다.
하지만 cancel
메서드 호출 후에 speak
메서드를 호출하면 제대로 utterance
가 쌓이지 않고 에러를 뱉어냅니다. 해당 현상을 해결하기 위해 아래와 같은 방법을 생각하고 시도해 보았습니다.
cancel
메서드 호출 이벤트 마무리 후에 호출되는 별도의 이벤트가 존재하는지 확인cancle
메서드 호출 후, setTimeout
을 이용하여 딜레이를 준 뒤 speak
메서드 호출utterance
객체에 onerror
이벤트로 speak
메서드 재호출 콜백 함수를 전달결과적으론 위 세가지 방법으로도 해결할 수 없었고, cancel
메서드를 사용하는건 위험하다고 판단하여 사용하지 않는 방향으로 로직을 변경하여 개발하였습니다.
onClick
이벤트에 조건문을 추가하여 발화도중 또 다른 발화를 막는다.문제점2. Web Speech API 기능을 제공하지 않는 브라우저 처리