input file 속성
readAsDataURL
css filter 속성
canvas.getContext("2d");
childNodes
이번 첼린지는 파일 이미지를 활용하여 필터 기능을 만들어 보고자 하였다. 그러기 위해선 input 속성 file
을 활용하면 된다. 공식 문서를 확인 해보면 input type에 file속성을 부여하면 나의 로컬스토리지 안에 있는 파일을 불러 올 수 있다. 또한 accept을 사용하여 이미지의 확장자 설정이 가능하다.
https://developer.mozilla.org/ko/docs/Web/HTML/Element/Input/file
그리고 또 하나 이미지를 브라우저에 출력 하기 위해선 readAsDataURL
메서드를 사용해야 한다. 이메서드는 컨텐츠를 특정 Blob 이나 File에서 읽어 오는 역할을 한다. 즉 해당 이미지의 url을 받을수 있다.
https://developer.mozilla.org/ko/docs/Web/API/FileReader/readAsDataURL
필터 기능은 css속성의 filter를 활용하였다. css에 다양한 filter 속성들이 있다.
https://developer.mozilla.org/ko/docs/Web/CSS/filter
해당 영역의 이미지를 캡쳐하기 위해 canvas를 사용하였고 cdn방식으로 설치하고 사용하였다. filter적용에 대한 내용은 아래 링크 참고하자
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter
childNodes
를 활용하면 첫 번째 자식 노드에 index 가 할당된 지정된 요소의 자식의 Node를 반환한다. 필터이미지 생성 후 다시 생성 할 때 기존에 생성한 필터이미지를 지우기 위해 이 키워드를 활용하였다.
https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
우선 css속성에서 filter속성의 값들을 배열에 담았고 배열에 길이 만큼 이미지가 생성 될 수 있도록 하였다. 여기서 중요한 점은 reader.readAsDataURL(file);
활용하여 파일 url을 받아 오는 것이다. 두번째는 onload
하면서 myImg
에 필터 값을 적용하여 반복문을 돌리는 것이다.
//index js
//...
const imgFilters = [
"",
"grayscale(100%)",
"sepia(100%)",
"saturate(8)",
"hue-rotate(90deg)",
"brightness(150%)",
"contrast(200%)",
"invert(100%)",
"opacity(50%)",
"blur(5px)",
];
input.addEventListener("change", (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
for (let i = 0; i < imgFilters.length; i++) {
const myimg = document.createElement("img");
myimg.src = reader.result;
myimg.classList.add("filterImg");
myimg.style.filter = imgFilters[i];
imgList.appendChild(myimg);
}
};
});
//...
그러면 배열의 길이만큼 필터가 적용된 이미지를 출력 할 수 있다.
클릭시 선택된 이미지를 새롭게 생성해서 selectImg
(div)에 넣었다. 여기서 중요한 점은 if문 childNodes
이다. childNodes
를 활용해서 부모요소 selectImg
의 자손 요소를 조회 할 수 있으며 길이가 1보다 크면 먼저 선택한 자손요소가 삭제 될 수 있도록 하였다.
const select = document.createElement("img");
imgList.addEventListener("click", (e) => {
const img = e.target;
select.src = img.src;
select.id = "select";
select.style.filter = img.style.filter;
selectImg.appendChild(select);
container.appendChild(saveBtn);
if (selectImg.childNodes.length > 1) {
selectImg.removeChild(selectImg.childNodes[0]);
}
});
이로서 필터 이미지를 선택할 때마다 선택 된 이미지만을 볼 수 있게 되었다.
필터 된 이미지를 저정하기 위해 저번 아트보드 첼린지때 사용했던 코드를 활용하였다. 하지만 필터값이 적용된 이미지가 아닌 원본 이미지가 계속 나왔다. 그래서 공식문서를 확인 하던 중 변수 ctx
에 filter값을 줄 수가 있었고 그대로 적용하였더니 해결 되었다.
const saveBtn = document.createElement("button");
saveBtn.innerText = "저장";
saveBtn.classList.add("saveBtn");
saveBtn.addEventListener("click", () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = select.src;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.filter = select.style.filter;
ctx.drawImage(img, 0, 0);
saveAs(canvas.toDataURL("image/jpg"), "image.jpg");
};
});
function saveAs(url, filename) {
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
}
개인적으로 canvas
를 잘 활용한다면 다양한 사이드 프로젝트를 만들수 있을 것 같다.
이번 첼린지는 여러모로 고생을 했다. 필터값 적용 안되는 것 부터해서 어떻게 필터값을 적용해서 할지 다양한 고민을 한 것 같다. 그중에서 조금 고민을 많이 했던 부분은 필터 이미지를 생성한 후 다시 이미지를 생성하면 중복해서 값들이 쌓이게 되는 경우 였다.
input.addEventListener("click", (e) => {
const filterImg = document.querySelectorAll(".filterImg");
filterImg.forEach((img) => {
img.remove();
select.remove();
saveBtn.remove();
});
});
그래서 위와 같이 한번 더 클릭을 하면 이미지와 선택한 이미지를 초기화 할 수 있도록 코드를 작성 하였다. 이로서 필터이미지를 생성하고, 다시 이미지를 생성하면 기존에 있는 이미지들을 지워지고 새로 만든 필터이미지들이 생성 된다.
// index.js
const container = document.getElementById("container");
const imgList = document.createElement("div");
const selectImg = document.createElement("div");
imgList.className = "imgList";
selectImg.id = "selectImg";
const label = document.createElement("label");
label.innerText = "이미지 필터 생성하기!";
label.htmlFor = "file-input";
label.classList.add("label");
container.appendChild(label);
const input = document.createElement("input");
input.type = "file";
input.accept = "image/*";
input.id = "file-input";
input.classList.add("input");
container.appendChild(input);
const imgFilters = [
"",
"grayscale(100%)",
"sepia(100%)",
"saturate(8)",
"hue-rotate(90deg)",
"brightness(150%)",
"contrast(200%)",
"invert(100%)",
"opacity(50%)",
"blur(5px)",
];
input.addEventListener("change", (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
for (let i = 0; i < imgFilters.length; i++) {
const myimg = document.createElement("img");
myimg.src = reader.result;
myimg.classList.add("filterImg");
myimg.style.filter = imgFilters[i];
imgList.appendChild(myimg);
}
};
});
container.appendChild(imgList);
container.appendChild(selectImg);
input.addEventListener("click", (e) => {
const filterImg = document.querySelectorAll(".filterImg");
filterImg.forEach((img) => {
img.remove();
select.remove();
saveBtn.remove();
});
});
const select = document.createElement("img");
imgList.addEventListener("click", (e) => {
const img = e.target;
select.src = img.src;
select.id = "select";
select.style.filter = img.style.filter;
selectImg.appendChild(select);
container.appendChild(saveBtn);
if (selectImg.childNodes.length > 1) {
selectImg.removeChild(selectImg.childNodes[0]);
}
});
const saveBtn = document.createElement("button");
saveBtn.innerText = "저장";
saveBtn.classList.add("saveBtn");
saveBtn.addEventListener("click", () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = select.src;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.filter = select.style.filter;
ctx.drawImage(img, 0, 0);
saveAs(canvas.toDataURL("image/jpg"), "image.jpg");
};
});
function saveAs(url, filename) {
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
}
https://github.com/fake-dp/Js-Challenge14-Mini-Project/tree/main/ImgFilter
배포링크