前端切图学习-响应式图片展示卡

Expanding Cards

Posted by R1NG on August 17, 2021 Viewed Times

响应式图片展示卡 Expanding-Cards

1. 概述

项目本体为一个响应式图片展示卡, 所展示的数张图片会随着用户点选而自动展开/折叠, 同时伴有描述文字的字体大小/字体粗细变化以及图片明暗度变化的平滑动画效果. 图片展示卡的左上角和右下角设置有两个链接. 背景和链接字体颜色受用户端操作系统内建的明暗模式控制, 会自动与之保持一致.

在本项目中, 将涉及如下知识点:

  1. 如何在一个 div 里垂直并水平居中子div
  2. 如何使用 position-absolute 绝对定位, 并了解其定位规则
  3. 初步了解 css 选择器
  4. 使用 transition 控制渐变动画的时间和切换类型
  5. 利用 @media 控制响应式排版和自动浅色/深色模式切换

效果:

20210818072420

20210818072514


2. 结构与切图

网页的基本结构如下:

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
<body>
    <h3 class="goBack" onclick="self.location.href='webSite 1">
        左上角文字链接
    </h3>
    <div class="container">
        <h3 class="motto" onclick="self.location.href='webSite 2'">
            右下角文字链接
        </h3> 

        <div class="panel active" style="background-image: url('avatar0.JPG');">
            <h3>第一图描述文字</h3>
        </div>   

        <div class="panel" style="background-image: url('./avatar1.JPG');">
            <h3>第二图描述文字</h3>
        </div>   

        <div class="panel" style="background-image: url('./avatar2.JPG');">
            <h3>第三图描述文字</h3>
        </div>   

        ...

    </div>
</body>

Screen-Recording-2021-08-18-at-07.32.09

每一张图片和它对应的描述文字一起包含在一个 div 中, 这些 panel 类的子 div 又被统一包裹在一个作为容器的父 div 中, 其中该 container 类的父 div 水平, 垂直居中. 两个文字链接分别以 bodycontainer 为其父元素 绝对定位.


3. 编写 CSS 样式

按照需求我们需要分别为不同类编写 CSS 样式:

1
2
3
4
5
6
7
8
9
body{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
    overflow: hidden;
    margin: 0;
}

在这里, 我们 使用弹性布局 display: flex 结合 align-items: centerjustify-content: center 实现对 container 的水平, 垂直居中.

同时, 我们设定 margin: 0 避免在某些浏览器中显示该页面时出现白边.


1
2
3
4
5
.container{
    display: flex;
    width: 90vw;
    position: relative;
}

我们将容器的 display 方式设为 flex 使得其子 div 基于 Flex 布局沿容器的水平主轴排列.

为了使右下角的文字链接在使用绝对定位时将该容器而不是 body 作为其参考父元素, 我们设定 position: relative.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.panel{
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    background-color: rgba(0, 0, 0, .5);
    background-blend-mode: multiply;
    height: 80vh;    
    color: #fff;
    cursor: pointer;
    flex: 0.5;
    margin: 5px;
    position: relative;
    transition: all .4s ease;
    overflow: hidden;
}
.panel.active {
    flex:4;
    background-color: rgba(0, 0, 0, 0);
}

下面进行图片面板的样式设定. 为了确保图片完全覆盖整个 div, 我们设定 background-size: cover 并将其位置设为 垂直居中. 为了避免图片在尺寸较大的屏幕上展示多次, 我们规定 background-repeat: no-repeat.

为了实现图片面板选中时展开, 未选中时折叠的效果, 我们调用 flex属性 规定它在这两种不同情况下如何增大/缩小自身宽度.

此外, 我们希望图片面板在未被选中时为图片加上一层阴影遮罩. 要实现这一效果, 我们有以下三种可选方案:

  1. 通过定位叠加: ~~~html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~~~css
.panel-outer {
    position: relative;
    width: 1200px;
    height: 400px;
    background: rgba(0, 0, 0, .5);
}
 
.panel-inner {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: url(avatar.jpg) no-repeat center center;
    background-size: cover;
    z-index: -1;
}
  1. 通过 CSS 伪类叠加: ~~~html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
~~~css
.panel {
    position: relative;
    width: 1200px;
    height: 400px;
    background: url(avatar.jpg) no-repeat center center;
    background-size: cover;
}
 
.panel::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    background-color: rgba(0, 0, 0, .5);
    z-index: 2;
}

关于 CSS 伪类, 详见CSS伪类和伪元素的用法 以及 CSS伪类的实例.

  1. 利用 CSS3 的特性: 正片叠底:
    1
    2
    3
    4
    5
    6
    7
    8
    
    .panel{
     background-size: cover;
     background-position: center;
     background-repeat: no-repeat;
     background-color: rgba(0, 0, 0, .5);
     background-blend-mode: multiply;
     height: 80vh;   
    }
    


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.panel h3 {
    font-size: 24px;
    font-weight: 400;
    position: absolute;
    bottom: 20px;
    left: 20px;
    margin: 0;
    opacity: 60%;
}
.panel.active h3 {
    font-size:30px;
    font-weight: 900;
    opacity: 1;
    transition: all 0.2s ;
}

随后我们设定图片面板上描述文字的样式. 为了实现图片面板被选中时其描述文字的强调效果, 我们分别对图片面板被选中和未被选中时, 位于其内部的所有 <h3> 元素样式进行分别规定. 此处注意 css 选择器语法 的使用:

为了确保文字始终位于图片面板左下角, 很自然地我们需要使用绝对定位.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
.motto{
    position: absolute;
    bottom: -50px;
    right: 5px;
    cursor: pointer;
}
.goBack{
    position: absolute;
    top: 0.5vh;
    left: 5.5vw;
    font-size: 2em;
    margin-bottom: 10px;
    transition: .3s ease;
}

接下来设定文字链接的样式. .motto 为位于图片面板容器右下角的文字链接, 其位置以容器为父元素绝对定位; .goBack 为左上角的文字链接, 以 body 为父元素绝对定位.


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
31
32
33
34
35
36
37
38
39
@media (max-width: 480px) {
    .container{
        width: 100vw;
    }
    .panel:nth-of-type(4),
    .panel:nth-of-type(5){
        display: none;
    }
}
@media (max-height: 400px) {
    .motto{
        display: none;
    }
}
@media (max-height: 700px){
    .goBack{
        display: none;
    }
}

@media (prefers-color-scheme: dark) {
    body {
        background-color: rgb(46, 46, 46);
    }
    .motto{
        color: white;
    }
    .goBack{
        color: white;
    }
}
@media (prefers-color-scheme: light) {
    body {
        background-color: white;
    }
    .motto{
        color: black;
    }
}

最后我们处理整个样式表中最有意思的部分: 自适应浅色/暗黑模式切换和页面响应式. 此处的 CSS 代码作用不言自明, 故不做赘述.

需要注意的是, 在本项目的 CSS 表中, 还涉及到下列内容:

  1. 我们可以使用 cursor: pointer 强制指定鼠标光标移到某元素上方时显示的光标类型为 pointer.
  2. 使用 overflow: hidden 控制某元素内的子元素在溢出时, 溢出部分隐藏.
  3. 使用 user-select: none 禁止用户选中对应元素内的文本, SafariWebKit 内核浏览器需要使用-webkit-user-select: none.
  4. 使用transition: all .4s ease 控制对应元素的过渡动画覆盖范围, 时间和类型.
  5. 使用 opacity: 1 控制元素不透明度.

完整的 CSS 样式表如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<style>
        *{
            box-sizing: border-box;
        }
        
        body{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100vh;
            overflow: hidden;
            margin: 0;
        }

        h3{
            user-select: none;
            -webkit-user-select: none;
        }
        
        .container{
            display: flex;
            width: 90vw;
            position: relative;
        }

        .panel{
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            background-color: rgba(0, 0, 0, .5);
            background-blend-mode: multiply;
            height: 80vh;    
            color: #fff;
            cursor: pointer;
            flex: 0.5;
            margin: 5px;
            position: relative;
            transition: all .4s ease;
            overflow: hidden;
        }
        .panel.active {
            flex:4;
            background-color: rgba(0, 0, 0, 0);
        }

        .panel h3 {
            font-size: 24px;
            font-weight: 400;
            position: absolute;
            bottom: 20px;
            left: 20px;
            margin: 0;
            opacity: 60%;
        }
        .panel.active h3 {
            font-size:30px;
            font-weight: 900;
            opacity: 1;
            transition: all 0.2s ;
        }

        .motto{
            position: absolute;
            bottom: -50px;
            right: 5px;
            cursor: pointer;
        }
        .goBack{
            position: absolute;
            top: 0.5vh;
            left: 5.5vw;
            font-size: 2em;
            margin-bottom: 10px;
            transition: .3s ease;
        }

        @media (max-width: 480px) {
            .container{
                width: 100vw;
            }
            .panel:nth-of-type(4),
            .panel:nth-of-type(5){
                display: none;
            }
        }
        @media (max-height: 400px) {
            .motto{
                display: none;
            }
        }
        @media (max-height: 700px){
            .goBack{
                display: none;
            }
        }

        @media (prefers-color-scheme: dark) {
            body {
                background-color: rgb(46, 46, 46);
            }
            .motto{
                color: white;
            }
            .goBack{
                color: white;
            }
        }
        @media (prefers-color-scheme: light) {
            body {
                background-color: white;
            }
            .motto{
                color: black;
            }
        }
    </style>


4. JavaScript

最后我们需要编写 JavaScript 函数监测图片面板的点选状态, 并相应地通过修改面板的 className 切换其样式, 结合 transition 实现无缝的动画效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// declare an object (NodeList) containing every DOM element which class == 'panel'
        const panels=document.querySelectorAll('.panel')
        
        // add event listeners to each element
        panels.forEach(panel => {
            // first reset every element's className to 'panel'
            // then append 'active' type to its class if it is clicked
            panel.addEventListener('click', () => {
                removeActiveClasses()
                panel.classList.add('active')
            })
        })

        // reset every element's className to 'panel'
        function removeActiveClasses() {
            panels.forEach(panel => {
                panel.classList.remove('active')
            })
        }

最后, 完整的网页演示可见 此处