本篇博客用于讲解如何实现图片九宫格变幻的样式效果,将图片分为九块填充在3×3的的九宫格子元素中,并结合grid
、hover
、transition
等CSS属性,实现元素hover
时,九宫格子元素合并为一张完整图片的动画效果。
为了简化代码,demo中通过JS设置CSS变量的方法,优化了元素背景的设置过程,减少了代码的繁杂度。
最后还结合js的点击事件实现了一个简易的点击拼图demo。
① 首先在一个父元素内,创建九个子元素,并通过display: grid;
实现3×3的九宫格布局,使其聚集在父元素中间,此时子元素之间无间隙。
② 给这九个子元素通过background
的相关属性,分别设置各个子元素的背景图片为总图片的一部分(1/9),其聚集在一起时,正好能够组成一张完成的图片。
③ 在设置子元素背景时,可以通过依次设置九个子元素的背景样式来实现,但是代码过于繁琐。为了优化代码,可以通过js获取相应元素,然后遍历获取到的元素设置CSS变量,最后再统一通过子级选择器+CSS变量的形式,统一设置元素背景。
④ 设置好背景后,利用父元素grid
布局的gap
属性,统一设置子元素之间的间距,使九个子元素按照固定间距,形成九宫格布局。
⑤ 父元素hover
时,修改gap
属性的值,使子元素再次聚集在一起,并通过transition
实现缓动动画。
核心思路为:变化主体为父元素,子元素不改变,改变父元素控制间隙的属性。
<style>
.d1 {
/* 设置grid布局 */
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
/* 设置子元素间隔 */
gap: 20px;
width: 340px;
height: 340px;
/* 设置子元素居中在父元素中心 */
justify-content: center;
align-content: center;
cursor: pointer;
/* 设置缓动动画 */
transition: all 0.3s linear;
border: 2px solid red;
}
/* hover时消除子元素间隔 */
.d1:hover {
gap: 0;
}
/* 统一设置子元素的背景 */
.d1>div {
background: url(../image/test.jpg) no-repeat;
/* 设置背景图片大小 这个大小等于后面拼接成的完成图片的大小 */
background-size: 300px 300px;
/* 利用JS设置的CSS变量控制子元素的背景 */
background-position: var(--bgx, 0) var(--bgy, 0);
}
style>
<div class="d1">
<div class="d1-1">div>
<div class="d1-2">div>
<div class="d1-3">div>
<div class="d1-4">div>
<div class="d1-5">div>
<div class="d1-6">div>
<div class="d1-7">div>
<div class="d1-8">div>
<div class="d1-9">div>
div>
<script>
// 获取所有子元素
const d1List = document.querySelectorAll('.d1 > div');
// 遍历所有子元素
d1List.forEach((item, index) => {
// 计算当前子元素所在行 0~2
const row = Math.floor(index / 3);
// 计算当前子元素所在列 0~2
const col = index % 3;
console.log(row, col);
// 根据子元素所在行和列 设置CSS变量 决定显示的背景图片的偏移量 从而决定背景图片的内容
item.style.setProperty('--bgx', `${-col * 100}px`);
item.style.setProperty('--bgy', `${-row * 100}px`);
})
script>
① 首先在一个父元素内,创建九个子元素,并通过display: grid;
实现3×3的九宫格布局,初始设置子元素的宽高小于grid
布局设置的单元格宽高,然后通过相关属性设置子元素在对应单元格内水平垂直居中。
② 给这九个子元素通过background
的相关属性,分别设置背景图片为总图片的一部分。
③ 在设置子元素背景时,可以通过依次设置九个子元素的背景样式来实现,但是代码过于繁琐。为了优化代码,可以通过js获取相应元素,然后遍历获取到的元素设置CSS变量,最后再统一通过子级选择器+CSS变量的形式,统一设置元素背景,使九个子元素各自占据背景图片的一部分。但由于此时子元素的宽高小于grid
布局设置的单元格宽高,所以此时子元素之间存在间隙,而且所有子元素组合拼接的背景图片并不完整。
④ 设置父元素hover
时,改变子元素的宽高,使其充满单元格宽高,承载的背景图片比例也相应变大,此时子元素之间紧密连接,并组合拼接为一张完整的图片。
核心思路为:变化主体为子元素,子元素改变宽高属性,父元素不变。
<style>
.d2 {
/* 设置grid布局 */
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
width: 300px;
height: 300px;
cursor: pointer;
border: 2px solid red;
}
.d2>div {
/* 设置子元素的初始宽高 */
width: 60px;
height: 60px;
/* 设置子元素位于grid布局网格的中心位置 */
justify-self: center;
align-self: center;
/* 设置子元素缓动动画 */
transition: all 0.3s linear;
/* 设置子元素背景图片 */
background: url(../image/test.jpg) no-repeat;
/* 设置背景图片的大小 这个大小等于后面拼接成的完成图片的大小 */
background-size: 300px 300px;
/* 利用JS设置的CSS变量控制子元素的背景偏移 */
background-position: var(--bgx, 0) var(--bgy, 0);
}
/* 父元素hover之后 修改子元素的宽高 使子元素能连接在一起 */
.d2:hover div {
width: 100px;
height: 100px;
}
style>
<div class="d2">
<div class="d2-1">div>
<div class="d2-2">div>
<div class="d2-3">div>
<div class="d2-4">div>
<div class="d2-5">div>
<div class="d2-6">div>
<div class="d2-7">div>
<div class="d2-8">div>
<div class="d2-9">div>
div>
<script>
// 获取所有子元素
const d2List = document.querySelectorAll('.d2 > div');
// 遍历所有子元素
d2List.forEach((item, index) => {
// 计算当前子元素所在行 0~2
const row = Math.floor(index / 3);
// 计算当前子元素所在列 0~2
const col = index % 3;
console.log(row, col);
// 根据子元素所在行和列 设置CSS变量 决定显示的背景图片偏移量
item.style.setProperty('--bgx', `${-col * 100}px`);
item.style.setProperty('--bgy', `${-row * 100}px`);
})
script>
① 首先在一个父元素内,创建九个子元素,并通过display: grid;
实现3×3的九宫格布局,并且设置子元素之间存在固定间隙。
② 给每一个子元素都设置背景图片,并且设置背景图片的大小等于子元素的大小,使背景图片在子元素中完整展示。
③ 在父元素hover时,修改子元素之间的间隙为0,并且修改子元素的背景图片大小和背景图片的偏移量,使子元素只显示背景图片的一部分,并且所有子元素拼接起来时,能够构成一张完整的背景图片。
核心思路:同时改变父元素和子元素,改变父元素控制间隙的属性,改变子元素的背景属性。
<style>
.d4 {
/* 设置grid布局 */
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
/* 设置子元素之间的间隙 */
gap: 20px;
width: 340px;
height: 340px;
/* 设置子元素整体内容区域相对于父元素水平垂直居中 */
justify-content: center;
align-content: center;
cursor: pointer;
/* 设置父元素缓动动画 */
transition: all 0.4s linear;
border: 2px solid red;
}
.d4>div {
/* 设置子元素缓动动画 */
transition: all 0.4s linear;
/* 设置子元素的背景 背景图片大小适配元素大小 */
background: url(../image/test.jpg) no-repeat;
background-size: 100% 100%;
}
/* 父元素hover时 将子元素之间的间隙变为0 */
.d4:hover {
gap: 0;
}
/* 父元素hover时 修改子元素背景图片的大小 以及背景图片偏移量 */
.d4:hover>div {
/* 设置的背景图片大小 就是子元素拼接成一张图片时 图片的大小 */
background-size: 300px 300px;
/* 根据JS设置的CSS变量 设置背景图片的偏移量 */
background-position: var(--bgx, 0) var(--bgy, 0);
}
style>
<div class="d4">
<div class="d4-1">div>
<div class="d4-2">div>
<div class="d4-3">div>
<div class="d4-4">div>
<div class="d4-5">div>
<div class="d4-6">div>
<div class="d4-7">div>
<div class="d4-8">div>
<div class="d4-9">div>
div>
<script>
// 获取所有子元素
const d4List = document.querySelectorAll('.d4 > div');
// 遍历所有子元素
d4List.forEach((item, index) => {
// 计算当前子元素所在行 0~2
const row = Math.floor(index / 3);
// 计算当前子元素所在列 0~2
const col = index % 3;
console.log(row, col);
// 根据子元素所在行和列 设置CSS变量 决定显示的背景图片偏移量
item.style.setProperty('--bgx', `${-col * 100}px`);
item.style.setProperty('--bgy', `${-row * 100}px`);
})
script>
上面的三个案例Demo中,都是在父元素hover
时,通过控制CSS样式子元素进行整体变化,无法做到针对单个元素进行变化。如果我们结合JS的点击事件,以及子元素的定位属性,就可以实现针对单个元素的变化。比如实现简单的拼图游戏效果等等。
① 首先在一个父元素内,创建九个子元素,并通过display: grid;
实现3×3的九宫格布局,并且设置子元素之间无间隙,紧密结合在一起,并且在父元素中水平垂直居中。
② 通过JS获取所有子元素,然后遍历获取到的子元素,根据子元素所在行和所在列,设置表示背景偏移的CSS变量,最后再统一通过子级选择器+CSS变量的形式,统一设置元素背景,使九个子元素各自占据背景图片的一部分。
③ 在上一步遍历所有子元素时,根据子元素所在行和所在列,同步设置表示子元素偏移的CSS变量,然后再给子元素设置 position: relative;
相对定位,最后再统一通过子级选择器+CSS变量的形式,利用CSS变量设置子元素的偏移量,使其形成有监理的九宫格布局。
④ 在遍历所有子元素的同时,给所有子元素绑定点击事件,根据事件触发元素上的表示元素偏移量的CSS变量的值,决定元素该向内还是向外偏移,然后根据元素所在的行和列,决定具体的元素偏移量。
<style>
.d3 {
/* 设置grid布局 */
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
width: 340px;
height: 340px;
/* 设置子元素整体内容区域相对于父元素水平垂直居中 */
justify-content: center;
align-content: center;
cursor: pointer;
border: 2px solid red;
}
.d3>div {
/* 设置子元素的偏移 使子元素之间出现间隙 形成九宫格布局 */
position: relative;
/* 利用JS的定义的CSS变量设置元素偏移量 */
left: var(--pleft, 0);
top: var(--ptop, 0);
z-index: 1;
width: 100px;
height: 100px;
/* 设置子元素的背景图片 */
background: url(../image/test.jpg) no-repeat;
/* 设置背景图片尺寸 该尺寸等同于子元素拼接后图片的尺寸 */
background-size: 300px 300px;
/* 利用JS的定义的CSS变量设置背景图片的偏移量 */
background-position: var(--bgx, 0) var(--bgy, 0);
/* 设置子元素的缓动动画 */
transition: all 0.3s linear;
}
style>
<div class="d3">
<div class="d3-1">div>
<div class="d3-2">div>
<div class="d3-3">div>
<div class="d3-4">div>
<div class="d3-5">div>
<div class="d3-6">div>
<div class="d3-7">div>
<div class="d3-8">div>
<div class="d3-9">div>
div>
<script>
// 获取所有子元素
const d3List = document.querySelectorAll('.d3 > div');
// 遍历所有子元素
d3List.forEach((item, index) => {
// 计算当前子元素所在行 0~2
const row = Math.floor(index / 3);
// 计算当前子元素所在列 0~2
const col = index % 3;
// 根据子元素所在行和列 设置CSS变量 决定显示的背景图片偏移量
item.style.setProperty('--bgx', `${-col * 100}px`);
item.style.setProperty('--bgy', `${-row * 100}px`);
// 根据子元素所在行和列 设置CSS变量 决定元素的偏移量
item.style.setProperty('--pleft', `${(col - 1) * 20}px`);
item.style.setProperty('--ptop', `${(row - 1) * 20}px`);
// 为当前子元素添加点击事件
item.addEventListener('click', (e) => {
// 获取当前子元素表示元素偏移量的CSS变量值
const left = e.target.style.getPropertyValue('--pleft');
const top = e.target.style.getPropertyValue('--ptop');
// 判断当前子元素是否在原始位置
if (left === '0px' && top === '0px') {
// 如果在原始位置 则根据子元素所在行和列 设置CSS变量 决定使元素偏移
e.target.style.setProperty('--pleft', `${(col - 1) * 20}px`);
e.target.style.setProperty('--ptop', `${(row - 1) * 20}px`);
} else {
// 如果不在原始位置 则将CSS变量值设置为0px 将其移回原始位置
e.target.style.setProperty('--pleft', `0px`);
e.target.style.setProperty('--ptop', `0px`);
}
});
})
script>