-
[ JS ] Layered illustJavascript/실습 2023. 3. 1. 19:25반응형
layered illust는 '마우스와 상호작용하는 이미지를 만들어 볼까?'라는 생각이 들어 만들게 되었습니다. 요소에 적용된 동작은 마우스 오버에 따른 회전, 마우스 아웃에 따른 바운스 효과, 캔버스를 통한 비 내리는 효과 등이 있습니다.
See the Pen illust_layered by OnnJE (@ogtitle) on CodePen.
목표로한 동작은 요소가 마우스를 바라보는 방향으로 3차원 회전을 하는 것입니다. 이러한 3D Rotation을 구현하기 위해서 생각한 방법은 요소 앞에 그리드 패널을 씌워 마우스의 위치를 특정하고 각 그리드에 대응하는 변환을 적용하는 것입니다. 따라서, 그리드 패널을 가운데 그리드가 (0, 0)인 좌표 평면으로 취급하여, 각 그리드에 마우스 오버 이벤트를 추가해 마우스의 위치를 특정하고 동작할 함수를 구현합니다.
1. constructor
constructor(gridPanel, targetElement) { this.gridPanel = gridPanel; this.gridPanelStyle = getComputedStyle(this.gridPanel); this.gridRowNum = this.gridPanelStyle .getPropertyValue("grid-template-rows") .split(" ").length; this.gridColNum = this.gridPanelStyle .getPropertyValue("grid-template-columns") .split(" ").length; this.targetElement = targetElement; this.currentX, this.currentY; }
RotateWithMouse 클래스는 인스턴스를 생성할 때 그리드 패널과 애니메이션이 적용될 요소를 인자로 받습니다. 생성자에서는 인자로 받은 그리드 패널의 그리드 수를 계산해 이를 변수에 저장하고 클래스에서 사용될 this.targetElement를 초기화합니다. this.currentX, this.currentY는 이후 바운싱 효과를 구현할 때 사용될 변수이며 현재 마우스 위치를 저장합니다.
2. 그리드 패널 구현 - setGrid()
setGrid() { if (this.gridColNum != this.gridRowNum) return console.log( "Error : the numbers of columns and rows is different" ); let face = document.createElement("div"); face.className = "face"; face.style.zIndex = "1"; for (let i = 0; i < this.gridRowNum ** 2; i++) { this.gridPanel.appendChild(face.cloneNode(false)); } }
setGrid() 메서드에선 그리드 판넬의 각 그리드에 div요소를 추가합니다. 첫 번째 라인의 if문에서는 그리드의 행, 열의 개수가 동일한지 검사하고 그렇지 않은 경우 에러 메시지를 출력합니다. 행과 열이 동일해야 하는 조건을 추가한 이유는 두 조건이 크기 다른 수를 가지게 될 시 회전 변환의 정도가 심하게 차이가 날 수 있기 때문입니다. 이후 각 그리드 패널을 이룰 div 요소를 생성한 후 for 문을 통해서 앞서 생성한 face를 복사하고 이를 그리드 패널에 추가합니다.
3. 3차원 회전 변환 구현 - setRotateAnimation()
setRotateAnimation() { if (this.gridColNum != this.gridRowNum) return console.log( "Error : the numbers of columns and rows is different" ); let layers = this.targetElement.children; let numOfLayers = layers.length; let layerGap = Math.abs( this.getTranslateValue(layers[0])["z"] - this.getTranslateValue(layers[1])["z"] ); let grids = this.gridPanel.children; this.targetElement.style.transformOrigin = `50% 50% -${ numOfLayers * layerGap * 0.6 }px`; Array.from(grids).forEach((ele, idx) => { let x = (idx % this.gridColNum) - ~~(this.gridColNum / 2); let y = -1 * (~~(idx / this.gridColNum) - ~~(this.gridColNum / 2)); ele.addEventListener("mouseover", (e) => { [this.currentX, this.currentY] = [x, y]; this.targetElement.style.transform = `rotate3d(${y}, ${x}, 0, ${ Math.max(Math.abs(x), Math.abs(y)) * 1.4 }deg)`; }); }); }
마우스에 따른 요소의 3차원 회전은 각 그리드 아이템에 마우스 오버 이벤트가 발생 했을 때 등록해 두었던 회전변환이 실행되도록 하여 구현하였습니다. 위 코드를 차례대로 살펴보면, 먼저 layerGap을 통해 각 레이어의 간격을 구하고 이를 통해 transform-origin의 z 값을 조정해 층층이 쌓인 레이어들의 중심에서 변환이 이루어지도록 했습니다. 이후 grid 패널의 자식 요소들을 .children을 통해 구해 grids 변수에 저장하였고 forEach로 순회하며 각 그리드 아이템에 마우스 오버 이벤트에 따른 회전변환을 구현했습니다. x, y는 위에 그림에서와 같은 좌표를 표현하기 위해 사용되며, rotate3d 속성을 통해 3차원 회전 변환에 대한 함수를 마우스 오버 이벤트에 등록하였습니다. (회전 변환에 대한 설명은 아래 링크에서 확인 가능합니다.)
4. 3차원 바운스 효과 구현 - setBounceAnimation()
setBounceAnimation() { this.gridPanel.addEventListener("mouseleave", (e) => { if ( Math.abs(this.currentX) === Math.floor(this.gridRowNum / 2) || Math.abs(this.currentY) === Math.floor(this.gridRowNum / 2) ) { this.targetElement.style.transform = `rotate3d(${-this .currentY}, ${-this.currentX}, 0, ${ Math.max(Math.abs(this.currentX), Math.abs(this.currentY)) * 0.8 }deg)`; setTimeout( () => { this.targetElement.style.transform = `rotate3d(${this.currentY}, ${this.currentX}, 0, ${Math.max(Math.abs(this.currentX), Math.abs(this.currentY)) * 0.4}deg)`; setTimeout( () => {this.targetElement.style.transform = `rotate3d(0, 0, 0, 0deg)`}, 250); }, 250); } }); }
마우스가 그리드 패널에서 떠날 때 요소가 튕기는 효과는 구현하고자 했지만 그리드 패널의 아이템에서 마우스가 떠날때 또한 그리드 패널에 등록한 함수를 트리거하는 문제점이 있었습니다. 이를 방지하고자 this.currentX 혹은 this.currentY가 테두리 아이템의 좌표와 동일 경우 이벤트 리스너에 등록된 함수가 실행되도록 하였습니다.
바운싱 효과는 파동이 감소하는 것과 같이 점차 줄어들게 하기 위해 세 단계로 나누어 작동합니다. 첫 번째 단계에서는 기존에 마우스 방향을 따라간 회전의 반대 방향으로 이전 변환의 반의 각도로 회전합니다. 두 번째 단계에선 그 반대로 이전 단계의 회전변환의 반만큼 회전하고, 세 번째 단계에선 회전변환이 0의 값, 즉, 원점으로 복귀합니다. 각 단계는 시간차를 두고 진행돼야 하기 때문에 setTimeOut을 사용했으며, 변환에 사용될 값들을 transform속성을 구한 결과인 변환 매트릭스를 계산해 구현해 보고자 하였지만 보다 단순하게 구현하는 게 나은 방법이라는 생각이 들어 단순히 this.currnetX, this.currentY 사용하는 것을 선택했습니다.
5. 비 내리는 효과 구현 - class Rain()
비 내리는 효과의 경우 이전에 작성한 코드를 사용했습니다. 자세한 설명은 아래 링크에서 확인 가능합니다.
반응형'Javascript > 실습' 카테고리의 다른 글
[ JS ] Pixel animaiton (0) 2023.03.01 [ JS / Canvas ] 비 내리는 효과 - rain effect (0) 2023.02.19