手写react优惠券组件

先看效果图

优惠卷

由于是截图,大小有些失真

实现分析

看到这个图,思考一下,就能明白,其实就两个难点:

  1. 左边的锯齿状是如何实现
  2. 中间的凹陷是如何实现

上述两个难点解决了,相信有 css 基础的都能写出这个组件。

实现锯齿效果

方法一:伪元素 before 和 after

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
.sawtooth {
/* 相对定位,方便让before和after伪元素绝对定位偏移 */
position: relative;
background:#e24141;
width:400px;
height:170px;
}

.sawtooth:before, .sawtooth:after {
content: ' ';
width: 0;
height: 100%;
/* 绝对定位进行偏移 */
position: absolute;
top: 0;
}

.sawtooth:before {
/* 圆点型的border */
border-right: 10px dotted white;
/* 偏移一个半径,让圆点的一半覆盖div */
left: -5px;
}

.sawtooth:after {
/* 圆点型的border */
border-left: 10px dotted white;
/* 偏移一个半径,让圆点的一半覆盖div */
right: -5px;
}


<div class="sawtooth"></div>

效果如下:
图片描述

讲解

这个就是在开头和最后画了一个点状边框,然后平移边框,让边框的一部分覆盖原来的边框,利用圆点的颜色和背景色一样的特点,制作锯齿效果。如果不平移边框效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
.sawtooth:before {
/* 圆点型的border */
border-right: 10px dotted white;
/* 偏移一个半径,让圆点的一半覆盖div */
left: 0;
}

.sawtooth:after {
/* 圆点型的border */
border-left: 10px dotted white;
/* 偏移一个半径,让圆点的一半覆盖div */
right: 0px;
}

图片描述

看了上图实现原理是不是一目了然了。但这也有一些缺点: 1.锯齿的颜色必须和背景色一样 2.无法画锯齿朝里的方式 ###方法二 radial-gradient 设置背景
###radial-gradient 讲解
用径向渐变创建图像。
简单语法:radial-gradient(circle, red 10px, blue 20px, yellow 30px);
形状是圆(也可以是椭圆),开始位置的颜色是 red,中间颜色是 blue,最后颜色是黄色。
10px 表示从圆心开始 10px 范围内都是红色;
20px 表示距离圆心 20px 的位置为 blue,然后向两边扩散,直到里面 10px 的红色区域,和向外 30px 地方的 yellow 区域;
30px 表示从 30px 开始往外都是 yellow。

1
2
3
4
5
6
.div {
margin: 20px;
height: 100px;
width: 100px;
background-image: radial-gradient(circle, red 10px, blue 20px, yellow 30px);
}

使用 radial-gradient 画圆点背景

  • 圆心设置成透明
  • 把过度颜色都设置成锯齿的颜色
  • 通过背景尺寸属性设置背景图的颜色,然后 repeate
1
2
3
4
5
6
7
.div {
margin: 20px;
height: 106px;
width: 140px;
background-image: radial-gradient(circle at center, transparent 6px, #28acff 7px);
background-size: 20px 15px;
}

图片描述

这样一个带圆点背景的 div 就出来了。然后通过设置宽度,只显示半个圆,左边的锯齿就出来了。width 设置成 10px 如下效果

图片描述

上边凹槽的实现

这个实现就比较简单了,通过绝对定位,用一个圆形元素覆盖父元素的边框。 ###问题:子元素无法覆盖父元素
在实现时遇到一个问题,就是子元素移动过去了,但是无法覆盖父元素的边框。这时,需要在组件外再套一层 div,这个 div 设置成相对定位,然后把圆 div 设置成相对定义,再移动位置就能覆盖里面的组件 div 了。 ##开发优惠卷
通过上述的讲解,需要实现优惠卷所需要的知识点就都讲完了,下面让我们来实现开始效果的优惠卷吧。 ###结构分析

  1. 一个 div 顶级容器,设置成相对定位。(解决无法覆盖问题)
  2. 一个 div 组件容器,放到上面的 div 中
  3. 锯齿 div(放到 2 中的的 div)
  4. 粗体显示折扣的 div(放到 2 中的的 div)
  5. 虚线 div(放到 2 中的的 div)
  6. 折扣详情 div(放到 2 中的的 div)
  7. 两个圆形 div,放到 1 或 2 中 div 都可以。

code

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
.parentContainer {
position:relative;
margin:20px;
overflow:hidden;
}
.container {
display:flex;
border:1px solid #ddd;
border-radius:3px;
width:300px;
height:105px;
border-left:0;
}
.left {
width:10px;
height:106px;
left:-1px;
border:0px solid #ddd;
border-radius:3px;
background-image:radial-gradient(circle at center,transparent 6px,#28ACFF 4px);
background-size:20px 15px;
z-index:1
}
.couponName {
text-align:center;
border:0px solid red;
line-height:106px;
font-size:40px;
font-family:PingFangSC-Medium;
font-weight:500;
color:rgba(40,172,255,1);
margin-left:20px;
margin-right:16px;
}
.subName {
font-size:20px;
}
.topSemicircle {
width:20px;
height:20px;
border:1px solid #ddd;
border-radius:10px;
position:absolute;
left:80px;
top:-16px;
padding:0;
background-color:#fff;
}
.bottomSemicircle {
width:20px;
height:20px;
border:1px solid #ddd;
border-radius:10px;
position:absolute;
left:80px;
bottom:-16px;
padding:0;
background-color:#fff;
}
.dashed {
border:1px dashed #ddd;
margin-top:11px;
margin-bottom:11px;
}
.right {
display:flex;
flex-direction:column;
justify-content:center;
align-items:flex-start;
padding-left:10px;
}
.desc {
font-size:10px;
font-family:PingFangSC-Regular;
font-weight:400;
color:rgba(170,170,170,1);
margin-top:10px;
}



<div class="parentContainer">
<div class="container">
<div class="left"></div>
<div class="couponName">8<span class="subName">折</span></div>
<div class="dashed"></div>
<div class="right">
<div>折扣卷7.5折</div>
<div class="desc">400张</div>
<div class="desc">有效时间:2018.09.21-2018.10.21</div></div>
<div class="topSemicircle"></div>
<div class="bottomSemicircle"></div>
</div>
</div>

可以把代码赋值到下面的在线工具中看下效果

https://c.runoob.com/front-end/61

React Code

根据自己需要再写成 react 版本,就易如反掌了。

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//less
.parentContainer {
position: relative;
margin: 20px;
overflow: hidden;
}

.container {
display: flex;
border: 1px solid #ddd;
border-radius: 3px;
width: 312px;
height: 105px;
border-left: 0;
}

.left {
width: 10px;
height: 106px;
left: -1px;
border: 0px solid #ddd;
border-radius: 3px;
background-image: radial-gradient(
circle at center,
transparent 6px,
#28acff 4px
);
background-size: 20px 15px;
z-index: 1;
}

.leftInvalid {
.left;
background-image: radial-gradient(
circle at center,
transparent 6px,
#aaaaaa 4px
);
}

.couponName {
text-align: center;
border: 0px solid red;
line-height: 106px;
font-size: 40px;
font-family: PingFangSC-Medium;
font-weight: 500;
color: rgba(40, 172, 255, 1);
min-width: 62px;
margin-left: 20px;
margin-right: 16px;
}

.couponNameInvalid {
.couponName;
color: #aaaaaa;
}

.title {
font-size: 16px;
font-weight: 400;
color: rgba(51, 51, 51, 1);
}

.invalidTitle {
.title;
color: rgba(170, 170, 170, 1);
}

.subName {
font-size: 20px;
}

.semicircle {
width: 20px;
height: 20px;
border: 1px solid #ddd;
border-radius: 10px;
position: absolute;
left: 98px;
padding: 0;
background-color: #fff;
}

.topSemicircle {
.semicircle;
top: -16px;
}

.bottomSemicircle {
.semicircle;
bottom: -16px;
}

.dashed {
border: 1px dashed #ddd;
margin-top: 11px;
margin-bottom: 11px;
}

.right {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
padding-left: 10px;
}

.desc {
font-size: 10px;
font-family: PingFangSC-Regular;
font-weight: 400;
color: rgba(170, 170, 170, 1);
margin-top: 10px;
}

//组件代码
import React, { PureComponent } from 'react'
import styles from './index.less'

export default class CouponCard extends PureComponent {
render() {
const {
valid = true,
data = {
id: 2323,
couponDescription: '折扣卷8.5折',
validDate: '2018.08.22-2018.09.12',
number: 23,
amount: 8.5,
unit: '折',
},
} = this.props
const amounts = data.amount.toString().split('.')
return (
<div className={styles.parentContainer}>
<div className={styles.container}>
<div className={valid ? styles.left : styles.leftInvalid} />
<div className={valid ? styles.couponName : styles.couponNameInvalid}>
{amounts[0]}
<span className={styles.subName}>
{amounts[1] ? `.${amounts[1]}` : ''}
{data.unit}
</span>
</div>
<div className={styles.dashed} />
<div className={styles.right}>
<div className={valid ? styles.title : styles.invalidTitle}>
折扣卷{data.amount}
{data.unit}
</div>
<div className={styles.desc}>{data.number}张</div>
<div className={styles.desc}>有效时间:{data.validDate}</div>
</div>
<div className={styles.topSemicircle} />
<div className={styles.bottomSemicircle} />
</div>
</div>
)
}
}

参考链接

文章作者: wenmu
文章链接: http://blog.wangpengpeng.site/2020/02/28/%E6%89%8B%E5%86%99react%E4%BC%98%E6%83%A0%E5%88%B8%E7%BB%84%E4%BB%B6/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 温木的博客
微信打赏