| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 |
- #웹솔루션
- 해피씨지아이
- #jQuery
- #뉴스
- javascript
- 솔루션
- #홈페이지
- CSS
- 게시판
- #해피CGI
- #이미지
- 홈페이지
- #happycgi
- #쇼핑몰
- #CSS
- 홈페이지제작
- 이미지
- CGIMALL
- #솔루션
- #업종별
- 해피CGI
- #홈페이지제작
- 웹사이트제작
- 사이트제작
- php
- #cgimall
- happycgi
- 웹솔루션
- #image
- jquery
- Today
- Total
웹솔루션개발 26년 노하우! 해피CGI의 모든것
[해피CGI][cgimall] 마우스 오버로 확장되는 벤토 그리드 갤러리 본문
여러 이미지를 3×3 형태의 벤토 그리드로 배치하고, 사용자가 특정 이미지를 가리키면 해당 행과 열의 비율이 변경되도록 구성된 인터랙션 예제입니다.
GSAP의 Flip 플러그인을 활용해 레이아웃이 바뀌는 과정이 끊기지 않고 부드러운 애니메이션으로 표현됩니다.
선택된 이미지 영역이 커지면서 주변 이미지들은 자연스럽게 축소·재배치되어 사용자의 시선을 집중시키는 효과를 줍니다.
단순한 이미지 확대가 아니라 그리드 전체 구조가 함께 변화하기 때문에 일반 갤러리보다 역동적인 화면 구성이 가능합니다.
포트폴리오, 브랜드 이미지 쇼케이스, 작품 갤러리, 제품 비주얼 소개 영역 등에 활용하기 좋은 인터랙션 방식입니다.

HTML 구조
<div class="bento-container cols-1-3-1 rows-1-3-1">
<div data-column="0" data-row="0" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="1" data-row="0" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="2" data-row="0" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="0" data-row="1" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="1" data-row="1" class="bento-item center-piece">
<div class="image-overlay"></div>
</div>
<div data-column="2" data-row="1" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="0" data-row="2" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="1" data-row="2" class="bento-item">
<div class="image-overlay"></div>
</div>
<div data-column="2" data-row="2" class="bento-item">
<div class="image-overlay"></div>
</div>
</div>
CSS 소스
:root {
--bg-color: #f0f2f5;
--gap: 15px;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
/* The Bento Grid Logic */
.bento-container {
display: grid;
gap: var(--gap);
width: 90vw;
height: 90vh;
max-width: 1000px;
max-height: 800px;
}
/*
Specific classes for each event target
*/
.bento-container.rows-3-1-1 {
grid-template-rows: 3fr 1fr 1fr;
}
.bento-container.rows-1-3-1 {
grid-template-rows: 1fr 3fr 1fr;
}
.bento-container.rows-1-1-3 {
grid-template-rows: 1fr 1fr 3fr;
}
.bento-container.cols-3-1-1 {
grid-template-columns: 3fr 1fr 1fr;
}
.bento-container.cols-1-3-1 {
grid-template-columns: 1fr 3fr 1fr;
}
.bento-container.cols-1-1-3 {
grid-template-columns: 1fr 1fr 3fr;
}
.bento-item {
background-color: var(--card-color);
border-radius: 12px;
box-shadow: 0 4px 6px rgba(255, 252, 225, 0.25);
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
color: #333;
position: relative;
cursor: pointer;
overflow: hidden;
border: 1px solid rgba(255, 252, 225, 0.5);
img {
position: absolute;
inset: 0;
object-fit: cover;
width: 100%;
height: 100%;
z-index: 1;
}
.image-overlay {
position: absolute;
inset: 0;
background-color: #333;
opacity: 0;
z-index: 2;
}
}
/* Hover Effect: Scale up and bring to front */
.bento-item:hover {
z-index: 10;
}
JS 소스
console.clear();
gsap.registerPlugin(Flip);
const bentoContainer = document.querySelector(".bento-container");
const bentoItems = bentoContainer.querySelectorAll("[data-row]");
const rowClasses = ["rows-3-1-1", "rows-1-3-1", "rows-1-1-3"];
const colClasses = ["cols-3-1-1", "cols-1-3-1", "cols-1-1-3"];
// Remember 0 index for the classes array, same for the data attribute on the HTML elements
let activeColumn = 1,
activeRow = 1,
doFlip = false;
bentoItems.forEach((e, i) => {
const colNumber = e.dataset.column;
const rowNumber = e.dataset.row;
e.addEventListener("mouseenter", () => {
// console.log("Item", i, "column", colNumber, "row", rowNumber);
const state = Flip.getState(bentoItems);
if (colNumber !== activeColumn) {
bentoContainer.classList.remove(colClasses[activeColumn]);
bentoContainer.classList.add(colClasses[colNumber]);
activeColumn = colNumber;
doFlip = true;
}
if (rowNumber !== activeRow) {
bentoContainer.classList.remove(rowClasses[activeRow]);
bentoContainer.classList.add(rowClasses[rowNumber]);
activeRow = rowNumber;
doFlip = true;
}
doFlip &&
Flip.from(state, {
duration: 0.4,
ease: "power2.out",
onStart: () => (doFlip = false),
absolute: true
});
});
});
bentoContainer.addEventListener("mouseleave", () => {
// Toggle the boolean just in case
doFlip = true;
// Get the state
const state = Flip.getState(bentoItems);
// Update the classes in the container
bentoContainer.classList.remove(colClasses[activeColumn]);
bentoContainer.classList.remove(rowClasses[activeRow]);
bentoContainer.classList.add(colClasses[1]);
bentoContainer.classList.add(rowClasses[1]);
// Update the index values
activeColumn = 1;
activeRow = 1;
// Run the Flip Animation
Flip.from(state, {
duration: 0.4,
ease: "power2.out",
onStart: () => (doFlip = false),
absolute: true
});
});
'웹프로그램밍 자료실 > 기타 자료' 카테고리의 다른 글
| [해피CGI][cgimall] 이미지가 자연스럽게 전환되는 갤러리 (0) | 2026.10.28 |
|---|---|
| [해피CGI][cgimall] 입체적인 이미지 카드 캐러셀 (0) | 2026.10.12 |
| [해피CGI][cgimall] 움직이는 인풋 버튼 애니메이션 Material Radio Button (0) | 2026.10.08 |
| [해피CGI][cgimall] 스킬을 표현하기 좋은 애니메이션 소스 Stats animation (0) | 2026.10.07 |
| [해피CGI][cgimall] CSS로 구현하는 순차 등장 애니메이션 (0) | 2026.10.06 |

