고정 레이아웃과 canvas에 이미지 불러오는 부분은 따로 정리해두었으니 참고!!
이 예제에는 드래그모드와 클릭모드 총 2가지 모드와 지우개가 구현되어있다.
드래그모드는 마우스 누름 → 드래그 → 마우스 놓음 순서로 이벤트를 잡아서 누른순간부터 놓는 순간의 영역만큼 모자이크를 하는 것이고, 클릭 모드는 미리 정해진 영역에 클릭을 하면 그 영역만 모자이크가 되는 원리이다.
예제 설명은 드래그모드 위주로!! (드래그 모드가 더 어려웠음.. ㅠㅠ)
공통사항
1. 캔버스는 모두 3개가 필요하다.
- 원본이 그려질 캔버스(originCanvas)
- 모자이크가 되어질 캔버스(mozaicCanvas)
- 마우스의 움직임(드래그, 호버)에 따라 사각형을 그려줄 캔버스(hoverCanvas)
2. 지우개와 모자이크가 되는 방식은 모두 동일하다. (모자이크 되는 영역이 다를뿐이다.)
3. 이미지 불러오기와 고정 레이아웃은 다른 게시글에 설명이 있다.
드래그 모드의 핵심
드래그모드의 이벤트 순서
- 마우스 누름 (mousedown) → 시작좌표 획득
- 드래그 (mousemove) → hover영역에 영역을 알려주는 빨간색 네모상자를 그려주는 역할
- 마우스 놓음 (mouseup) → 끝좌표 획득 및 width와 height를 구하여 모자이크 실행
래스터 정보와 getImageData
이미지는 수많은 픽셀로 구성되어 있다. 이미지를 구성하는 픽셀 정보를 래스터(Raster)라고 하며 이미지 표면에 어떤 그림이 그려져 있는지를 저장한다. 래스터 데이터를 직접 조작하면 그리기 메서드로는 불가능한 효과를 낼 수 있다.
좌표와 width,height를 구하는 이유는 래스터 정보를 얻을 수 있는 getImageData(x, y, w, h) 메소드의 파라미터이기 때문이다.
전체코드
[HTML 코드 + CSS코드]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>테스트</title>
<link rel="stylesheet" href="reset.css">
<style>
html,body{
height: 100%;
}
.wrap{
min-width: 800px; /*전체 사이트의 길이가 800px보다 작아지지 않도록 설정*/
height: 100%;
}
.header{
height: 100px;
position: fixed;
top: 0; left: 0; right: 0;
background-color: aqua;
z-index: 100;
}
.container{
/* height를 %로 지정하기 위해서는 부모요소로 부터 높이값을 상속받아야한다. */
min-height: 100%;
margin: 0 auto;
margin-top: -100px;
padding-top: 200px; /* padding으로 해야 border-box를 사용하여 원하는 레이아웃을 만들 수 있다. */
box-sizing: border-box;
}
.content{
margin: 0 auto;
max-width: 1200px;
background-color: aliceblue;
}
.footer{
width: 100%;
min-height: 100px;
background-color: blue;
}
#canvas_area{
position: relative;
display: inline-block;
width: 100%;
height: 600px;
overflow: auto;
}
#canvas_area canvas{
position: absolute;
left: 0;
}
</style>
</head>
<body>
<div class="wrap">
<div class="header">
<div>
<button id="mode_change">모드변경</button>
<button id="eraser">지우개</button>
</div>
<div>
<input type="file" id="img_loading"></button>
</div>
</div>
<div class="container">
<div class="content">
<div id="canvas_area">
<canvas id="origin"></canvas> <!--원본-->
<canvas id="mosaic"></canvas> <!--모자이크 영역-->
<canvas id="hover"></canvas> <!--마우스영역-->
</div>
</div>
</div>
<div class="footer">footer</div>
</div>
</body>
<script>
...
</script>
</html>
[JS코드]
<script>
class Mozaic{
constructor(){
window.mode = 0; //드래그 모드
this.$originCanvas = document.querySelector("#origin");
this.$mosaiCanvasc = document.querySelector("#mosaic");
this.$hoverCanvas = document.querySelector("#hover");
this.$imgLoading = document.querySelector("#img_loading");
this.$eraser = document.querySelector("#eraser");
this.$modeChange = document.querySelector("#mode_change");
this.originCtx = this.$originCanvas.getContext('2d');
this.mosaicCtx = this.$mosaiCanvasc.getContext('2d');
this.hoverCtx = this.$hoverCanvas.getContext('2d');
this.eventBinding();
this.init();
}
//초기화
init(mode=0){
window.isErase = false;
window.isDrag = false;
window.sx=0, window.ex=0;
window.sy=0, window.ey=0;
this.hoverCtx.clearRect(0,0,this.$hoverCanvas.width, this.$hoverCanvas.height); //그려진 호버 영역 지우기
if(mode===1){ //클릭모드 초기화
//모자이크 영역 초기화
window.mozXSize = 15;
window.mozYSize = 15;
}
}
eventBinding(){
this.$imgLoading.addEventListener('change',this.loadImg.bind(this));
//드래그 ↔ 클릭 모드 변경
this.$modeChange.addEventListener('click',()=>{
window.mode = Number(!window.mode); //모드 체인지 (0:드래그[기본], 1:클릭)
this.isErase = false; //지우개 선택되었을 시 지우개 해제
this.init(window.mode);
console.log('현재 모드 : ', window.mode);
});
//지우개 클릭 : 지우개 모드 활성화
this.$eraser.addEventListener('click',()=>{
window.isErase = true;
});
this.$hoverCanvas.addEventListener('mousedown',(e)=>this.hoverCanvasMouseDown.call(this,e));
this.$hoverCanvas.addEventListener('mousemove',(e)=>this.hoverCanvasMouseMove.call(this,e));
this.$hoverCanvas.addEventListener('mouseup',(e)=>this.hoverCanvasMouseUp.call(this,e));
this.$hoverCanvas.addEventListener('mouseout',(e)=>this.hoverCanvasMouseOut.call(this,e));
this.$hoverCanvas.addEventListener('click',(e)=>this.hoverCanvasMouseClick.call(this,e));
}
//호버영역 마우스 벗어났을 때 핸들링
hoverCanvasMouseOut(e){
}
//호버영역 마우스 드래그 시작 (드래그 모드)
hoverCanvasMouseDown(e){
if(window.mode===0){ //드래그 모드인지 확인
window.isDrag = true;
window.sx = e.layerX;
window.sy = e.layerY;
this.hoverCtx.strokeRect(sx,sy,0,0);
}
}
//호버영역 마우스 클릭(클릭모드)
hoverCanvasMouseClick(e){
if(window.mode === 1){
//좌표가져오기
window.ex = Math.max(0, e.layerX);
window.ey = Math.max(0, e.layerY);
const imgData = this.originCtx.getImageData(window.ex,window.ey,window.mozXSize,window.mozYSize);
this.doMosaic(imgData,ex,ey);
}
}
//호버영역 마우스 드래그 끝 (드래그 모드)
hoverCanvasMouseUp(e){
if(window.mode === 0){ //드래그 모드
isDrag = false; //드래그끝을 알린다.
this.hoverCtx.clearRect(0,0,this.$hoverCanvas.width, this.$hoverCanvas.height); //표시된 드래그 영역 삭제
window.ex = e.layerX;
window.ey = e.layerY;
const startX = Math.min(window.sx, window.ex);
const startY = Math.min(window.sy, window.ey);
const endX = Math.max(window.sx,window.ex);
const endY = Math.max(window.sy,window.ey);
const imgData = this.originCtx.getImageData(startX,startY, endX - startX, endY - startY);
this.doMosaic(imgData,startX,startY);
}
}
//호버영역 마우스 이동 (드래그모드, 클릭 모드)
hoverCanvasMouseMove(e){
if(window.mode === 0){ //드래그 모드 확인
if(isDrag){ //드래그 중일때만 동작
this.hoverCtx.clearRect(0,0,e.target.width, e.target.height);
this.hoverCtx.strokeStyle = "#FF0000"; //빨간색으로 드래그 영역 잡히게 설정
this.hoverCtx.strokeRect(sx,sy, e.layerX - sx, e.layerY - sy);
}
}else if(window.mode === 1){ //클릭 모드
//좌표 가져오기
const x = Math.max(e.layerX,0);
const y = Math.max(e.layerY,0);
//기존 호버영역 그려진 것들 삭제
this.hoverCtx.clearRect(0,0,e.target.width, e.target.height);
this.hoverCtx.strokeStyle = 'black';
this.hoverCtx.strokeWidth = 30;
this.hoverCtx.fillStyle = 'rgba(0,255,255,0.5)';
this.hoverCtx.fillRect(x,y,window.mozXSize, window.mozYSize);
}
}
//이미지 불러오기
loadImg(){
const file = this.$imgLoading.files[0];
console.log(file);
if(!file.type.match(/image.*/)){
alert('이미지 파일이 아닙니다.');
return;
}
const reader = new FileReader();
reader.onload = (e)=>{
const img = new Image();
img.onload = ()=>{
this.$originCanvas.width = img.width;
this.$originCanvas.height = img.height;
this.$mosaiCanvasc.width = img.width;
this.$mosaiCanvasc.height = img.height;
this.$hoverCanvas.width = img.width;
this.$hoverCanvas.height = img.height;
this.originCtx.drawImage(img,1,1);
this.mosaicCtx.drawImage(img,1,1);
}
img.src = e.target.result;
}
reader.readAsDataURL(file);
}
}
new Mozaic();
</script>
reset.css를 다운로드 받아서 같은 경로에 놔두면 된다.
[결과물]
참고사이트
http://www.soen.kr/html5/html3/3-2-2.htm
'코딩이야기 > JS' 카테고리의 다른 글
[canvas] 로컬 이미지 파일 선택해서 캔버스에 가져오기 (with FileReader) (0) | 2022.10.25 |
---|---|
객체 DEEP COPY (0) | 2022.10.17 |
JavaScript 배열 랜덤 섞기 (0) | 2022.10.17 |