Input에 이미지 넣기!

jh_leitmotif·2021년 8월 13일
0

Frontend 개인 공부

목록 보기
7/24
post-thumbnail

🧐 개요

게시글 작성의 기능을 구현할 때 무지성(?)으로 textarea를 쓰던 저는

velog처럼 복사/붙여넣기로 이미지를 첨부하는 기능을 구현하고 싶어졌습니다.

다시보니 별 것 아닌 일이지만 온갖 삽질과 대공사를 기념해서

그 과정을 정리해봅니다.


😡 Input과 textarea의 한계

보통 글 작성이 필요한 경우

위처럼 <input type="text">를 쓰거나, <textarea>를 사용합니다.

하지만 이 두 태그는 글자만 작성할 수 있지, 이미지는 넣을 수가 없습니다.

물론 막말로 <img src='url'/>을 직접 작성할 순 있지만.. 말도 안되는 이야기입니다.


📋 contentEditable 속성의 발견

직역하면 '내용을 편집할 수 있음'을 의미하는 이 속성은

input, textarea 등의 태그 외에 편집하고 싶은 태그를 편집가능하게 해줍니다.

div 태그에 contentEditable='true'만 달아주면...

요로코롬, 이미지와 텍스트를 같이 작성할 수 있습니다.

따로 style을 주지 않았지만

style={{outline:'none';}}

을 통해 바깥 경계선을 보이지 않게할 수 있습니다.

관련하여 react-contentEditable 이라는 npm package가 있습니다.
onInput과 거의 다를바없는 것이긴 하나, 참고 자료로 링크 붙입니다.
참고 링크 : https://stackoverflow.com/questions/22677931/react-js-onchange-event-for-contenteditable


📋 state의 처리

input과 textarea와 다르게 onInput을 통해 state를 업데이트합니다.

❓ Difference between onChange() , onInput()

onChange()는 input, textarea, select 태그에만 사용가능합니다.
input 태그의 focus를 잃은 경우 onChange의 function이 발생합니다.
onInput()은 태그에 데이터가 들어올 때마다 function이 호출됩니다.


📋 게시글 조회 처리

저는 onInput() 동작을 통해 state에 innerHTML을 업데이트했습니다.

const divC = document.getElementById('contentDiv')
setDivContent(divC.innerHTML)

이 경우 저장되는 값은 다음과 같습니다.

데이터 컬럼에 DOM Element가 String 형태로 저장됩니다.

즉, 2차 가공을 하지 않으면 게시글을 조회할 때...

이런 방식으로 출력되버립니다.

따라서 Parser를 이용해 DOM 요소로 다시 변경해주어야 합니다.

❓ Convert to DOM Element

DOMParser를 이용해 String을 DOM Element로 변환할 수 있습니다.

doc = parser.parseFromString(str,'text/html')
위 코드는 DOM을 생성합니다.

<html>
 <head></head>
 <body>
  <String elements>
 </body>
</html>

따라서 const element = doc.body.childNodes 를 통해
글 내용만을 쏙 빼내 반환하면 됩니다.

❓ Appendchild to specific element

저는 반환한 요소를 특정 태그에 append하는 방식을 사용했습니다.

  1. async/await를 통해 먼저 전체 DOM을 그린다.
  2. DOM에서 글 내용에 해당하는 contentDiv에 html node를 추가한다.

appendChild 메서드는 Node 객체만 인자로 받습니다.
반환된 element는 NodeList 타입을 가지므로, 반복문을 통해
하나하나의 Node를 인자로 넣어야합니다.

예를 들면, 아래와 같습니다.

안녕!			// strToHTML(Rows.content)[0]
<div>디브!</div>		// strToHTML(Rows.content)[1]
<div>잘가!</div>		// strToHTML(Rows.content)[2]

// 각각은 object Node type입니다.
// 안녕~잘가!를 모두 포함하는 element는 object NodeList type입니다.

참고 링크 : https://developer.mozilla.org/ko/docs/Web/API/Node/appendChild


📋 이미지 업로드하기

"그냥 오른쪽 클릭, 다른 이름 저장하면 되는 거 아니에요?"

맞는 이야기지만, 경우에 따라 전체 페이지에 대한 오른쪽 클릭을 금지하고

공유하고 싶은 이미지 등의 파일만 다운로드받게 해야할 때가 있습니다.

서버사이드, DB의 로직은 다음 포스트에 정리하는 것으로 하고...

기본적으로 파일 업로드는

<input type="file" id="FileID" onChange={props.fileHandler}/>

위처럼 <input type="file"> 을 써야합니다.

하지만 이 방법은 <form> 태그를 통해 데이터를 전달할 때 사용하며

복붙한 이미지는 그저 String 형태로 DB에 저장될 뿐
request의 File 객체로 전달되지 않을 겁니다.

그러므로 String 형태의 img 태그를 File 객체로 만들어 전달해야합니다.

🔧 onInput()의 Handler 이용하기

앞서 onInput Handler에서 div 태그의 값을 업데이트했으므로

그대로 해당 함수에서 이미지 문자열 가공 작업을 처리합니다.


<img src="~" alt="~"> 안녕! <div>안녕! 2 </div>

이미지가 삽입된 글 내용은 위의 양식처럼 구성되므로..

  1. div 태그를 변수에 저장
  2. getElementsByTagName('img')를 통해 이미지 태그 확인
  3. fetch에 img src를 인자로 넘겨 promise 객체 반환
  4. res.blob()을 통해 blob 객체 확인

❓ What is blob?

blob 객체는 이미지, 오디오 등의 멀티미디어 오브젝트이거나, 바이너리 실행 코드를 가리킵니다. 데이터를 직접 받아오기보단, 간접적으로 접근하기 위한 객체입니다.

서버에서 request.file을 통해 업로드 로직을 진행하고 있습니다.

따라서 blob 객체를 그대로 넘기면 오류가 발생하므로, file type으로 변경할 필요가 있습니다.

딱히 어려울 것은 없이 객체의 lastModifiedData와 name만 지정해주면 file 객체로서 넘길 수 있었습니다.

참고 링크 : https://stackoverflow.com/questions/27159179/how-to-convert-blob-to-file-in-javascript

만약 blob.name을 따로 넣어주지 않으면 파일이 저장될 때
blob이라는 이름으로 저장되어버립니다.

따라서 사전에 첨부되는 파일의 originalName로 지정하거나
임의의 난수 등을 넣어도 될 것 같습니다.

한 가지 조심해야할 것은.

<input type="file"></input>

위 태그에서 받아오는 file.orignalname은 확장자를 포함하지만

blob객체의 name은 확장자를 포함하지 않습니다.

그러므로

blob.name = 'temp.'+blob.type.split('/')[1]

// blob.type은 image/jpeg, text/html 등 내용과 확장자로 구성됩니다.

애초에 blob.name에 확장자를 포함시켜 저장시켰습니다.

❓ 또 다른 방법?

상기 서술한 방법을 하기 전에 시도했던 방법이 있습니다.

img src값이 base64 string을 포함한다는 것에 착안했습니다.

  1. img src string을 base64로 변환
  2. base64 string을 파일로 변환하여 서버에 전달

다만 지속적으로 발생한 File 생성자 관련 오류로 인해 접어두고, 상기 blob 객체를 활용하는 방안으로 선회하게 되었습니다만

충분히 가능한 방법이라고 생각해 참고링크를 남깁니다.

참고링크 :
https://stackoverflow.com/questions/246801/how-can-you-encode-a-string-to-base64-in-javascript
https://stackoverflow.com/questions/44458874/how-to-base64-encode-inputs-in-react/44458974

// String to base64
https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
// base64 to file


🎯 마무리

미루고 미뤄왔던 글 작성시 이미지 넣기 작업이었습니다.

그 과정 중에 앞서 포스팅한 에러를 알게 되고... 해결하고...

잠깐 잊고 있던 async/await를 다시 활용하고... blob 객체를 알게되고..

꽤나 삽질을 거듭한 시간이었지만 '꽤 늘었을지도?' 란 생각을 하며
즐겁게 작업했습니다.

profile
Define the undefined.

0개의 댓글