目录
一、效果图
二、代码
1.直接使用_不和父组件传参
2.作为通用组件使用_和父组件传参
一、效果图
1.未选择任何选项时
2.悬浮效果
3.点击效果
4.选中选项的样式
5.选项太多时效果,(设置最大高度200,根据需要自行更改.popover-box样式的max-height即可)
二、代码
使用技术 react+scss+tsx ,如果你用 react+scss+jsx , 只需要将所有的类型注解(num:number 和 person:string)后面的类型去掉即可。只有函数的参数使用了类型注解,去掉即可。
新建一个文件夹 selecteOption,下面建两个文件 index.tsx,index.scss。
1.直接使用_不和父组件传参
index.tsx:
import './index.scss';
// 使用svg,后期更改图标颜色比较方便,还可以做悬浮变色。记得换成你自己路径下的图片
import { ReactComponent as Up } from '../../assets/images/icon-up.svg';
import { useState, useEffect } from 'react'
function SelectOption() {
// 选项body的滚动条
const [scrollWidth, setScrollWidth] = useState(0)
// 选项弹窗是否显示,为了优化用户体验,控制它的opacity透明度隐藏,为了使选项点击事件能够生效,选项弹窗立即隐藏
const [showOption, setShowOption] = useState(false)
// 选项弹窗是否显示,控制它的div是否存在,(弹窗隐藏后元素存在,点击事件生效)需要将弹窗移除
const [showOptionSetTimeout, setShowOptionSetTimeout] = useState(false)
// 当前选中的选项
const [selectedItem, setSelectedItem] = useState({
id: '',
item: ''
})
//挂载后,计算选项弹窗是否有滚动条,从而改变选项弹窗body宽度
function changeWidth() {
var myElement: any = document.getElementById('mySelecte');
var widthWithBorder = myElement.offsetWidth;
var widthWithoutBorder = myElement.clientWidth;
setScrollWidth(widthWithBorder - widthWithoutBorder);
}
useEffect(() => {
changeWidth();
}, []);
/**
* input元素获得焦点以及失去焦点的事件
*/
function showOptionBox() {
setShowOption(true)
setShowOptionSetTimeout(true)
}
/**
* 选择框失去焦点事件的处理逻辑
*/
function inputBlur() {
setShowOption(false)
console.log(showOption, "showOption")
setTimeout(() => {
setShowOptionSetTimeout(false)
}, 300);
}
/**
* 用户选中了某个选项
* @param id 区分选项的唯一标志
* @param item 选中的名称
*/
function selectedOption(id: number, item: string) {
setSelectedItem({
id: String(id), item
});
}
return (
<div className='so-all-box' >
<input type="button" onBlur={inputBlur} className={showOption ? 'so-box borderB' : 'so-box borderA'} id="mySelecte" onClick={() => { showOptionBox() }}>
</input>
<div className={selectedItem.id === '' ? "lr-selected-manage-around-text textA" : "lr-selected-manage-around-text textB"}>
{selectedItem.id === '' ? '请选择' : selectedItem.item}
</div>
{/* 箭头 */}
<Up className={showOption === true ? 'lr-selected-manage-around-icon-select arrow-down' : 'lr-selected-manage-around-icon-select arrow-up'}></Up>
{/* 选择元素的下拉框*/}
{showOptionSetTimeout ?
<div className={showOption ? 'popover-box popover-box-show' : 'popover-box popover-box-hidden'} id="mySelecte" style={{ width: `calc(100% + ${scrollWidth}px)` }}>
{['牛肉馅饼', '北京烤鸭', '蛋糕', '铁锅炖大鹅', '可乐鸡翅'].map((item, index) => (
<div key={index} className={selectedItem.item === item ? "popover-single popover-single-selected" : "popover-single"} onClick={() => { selectedOption(index, item) }}>{item}</div>
))}
</div>
: ''}
</div>
);
}
export default SelectOption;
index.scss
.so-all-box {
width: 360px;
position: relative;
.textA {
color: #A7AAA8;
}
.textB {
color: #1E201F;
}
.lr-selected-manage-around-text {
position: absolute;
top: 6px;
left: 12px;
}
.popover-box-show {
opacity: 1;
}
.popover-box-hidden {
opacity: 0;
}
.popover-box {
padding: 4px 0;
position: absolute;
top: 34px;
left: 0;
z-index: 10;
border-radius: 4px;
background: #FFF;
box-shadow: 0px 4px 15px 0px rgba(0, 0, 0, 0.12);
max-height: 200px; //更改选项卡最大高度
overflow: auto;
width: 100%;
.popover-single {
width: 100%;
height: 32px;
line-height: 32px;
cursor: pointer;
color: #505553;
font-size: 14px;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
&.popover-single-selected {
background: #E8FAF8;
color: #00b498;
}
&:hover {
background: #F2F5F4;
}
}
}
.borderA {
border: 1px solid #E8ECEB;
}
.borderB {
border: 1px solid #00b498;
}
.so-box {
width: 360px;
height: 34px;
line-height: 34px;
font-size: 14px;
padding: 2px 10px 0px 10px;
margin: 0;
border-radius: 6px;
box-sizing: border-box;
position: relative;
cursor: pointer;
background-color: #fff;
outline: none;
&:hover {
border: 1px solid #00b498;
}
}
.lr-selected-manage-around-icon-select {
width: 14px;
height: 14px;
position: absolute;
top: 10px;
right: 12px;
stroke: #A8ABB2;
font-size: 14px;
z-index: 9;
}
.arrow-up {
transform: rotateZ(-90deg); //根据箭头方向,自行调整角度,关闭时状态
}
.arrow-down {
transform: rotateZ(90deg); //根据箭头方向,自行调整角度,选项可出现时状态
}
}
使用方法:
//在父元素中引入
import SelectOption from '../selecteOption'
function Home() {
return (
<div>
<SelectOption></SelectOption>
</div>
);
}
export default Home;
2.作为通用组件使用_和父组件传参
封装为通用组件使用。scss样式文件和上面的一样。
index.tsx
import './index.scss';
// 使用svg,后期更改图标颜色比较方便,还可以做悬浮变色
import { ReactComponent as Up } from '../../assets/images/icon-up.svg';
import { useState, useEffect } from 'react'
function SelectOption(props: any) {
// 父组件传过来的选项组
let optionList = props.selectList
// 选项body的滚动条
const [scrollWidth, setScrollWidth] = useState(0)
// 选项弹窗是否显示,为了优化用户体验,控制它的opacity透明度隐藏,为了使选项点击事件能够生效,选项弹窗立即隐藏
const [showOption, setShowOption] = useState(false)
// 选项弹窗是否显示,控制它的div是否存在,(弹窗隐藏后元素存在,点击事件生效)需要将弹窗移除
const [showOptionSetTimeout, setShowOptionSetTimeout] = useState(false)
// 当前选中的选项,从父元素获得初始值
const [selectedItem, setSelectedItem] = useState(props.currentOption)
//挂载后,计算选项弹窗是否有滚动条,从而改变选项弹窗body宽度
function changeWidth() {
var myElement: any = document.getElementById('mySelecte');
var widthWithBorder = myElement.offsetWidth;
var widthWithoutBorder = myElement.clientWidth;
setScrollWidth(widthWithBorder - widthWithoutBorder);
}
useEffect(() => {
changeWidth();
}, []);
/**
* input元素获得焦点以及失去焦点的事件
*/
function showOptionBox() {
setShowOption(true)
setShowOptionSetTimeout(true)
}
/**
* 选择框失去焦点事件的处理逻辑
*/
function inputBlur() {
setShowOption(false)
console.log(showOption, "showOption")
setTimeout(() => {
setShowOptionSetTimeout(false)
}, 300);
}
/**
* 用户选中了某个选项
* @param id 区分选项的唯一标志
* @param item 选中的名称
*/
function selectedOption(id: number, item: string) {
setSelectedItem({
id: String(id), item
});
// 将用户点击结果返回给父元素
props.getOption({ id: String(id), item });
}
return (
<div className='so-all-box' >
<input type="button" onBlur={inputBlur} className={showOption ? 'so-box borderB' : 'so-box borderA'} id="mySelecte" onClick={() => { showOptionBox() }}>
</input>
<div className={selectedItem.id === '' ? "lr-selected-manage-around-text textA" : "lr-selected-manage-around-text textB"}>
{selectedItem.id === '' ? '请选择' : selectedItem.item}
</div>
{/* 箭头 */}
<Up className={showOption === true ? 'lr-selected-manage-around-icon-select arrow-down' : 'lr-selected-manage-around-icon-select arrow-up'}></Up>
{/* 选择元素的下拉框*/}
{showOptionSetTimeout ?
<div className={showOption ? 'popover-box popover-box-show' : 'popover-box popover-box-hidden'} id="mySelecte" style={{ width: `calc(100% + ${scrollWidth}px)` }}>
{optionList.map((item: any, index: any) => (
<div key={index} className={selectedItem.item === item ? "popover-single popover-single-selected" : "popover-single"} onClick={() => { selectedOption(index, item) }}>{item}</div>
))}
</div>
: ''}
</div>
);
}
export default SelectOption;
父组件调用:
//在父元素中引入
import SelectOption from '../selecteOption'
function Home() {
/**
* 下拉框子元素返回的用户点击的结果
* @param obj 返回的结果,是个对象,id唯一标志+item选中的name
*/
function getOptionFromSon(obj: Object) {
console.log(obj)
// 将结果传给后端
}
return (
<div>
<SelectOption selectList={['牛肉馅饼', '北京烤鸭', '蛋糕', '铁锅炖大鹅', '可乐鸡翅']} currentOption={{ id: '1', item: '北京烤鸭' }} getOption={getOptionFromSon}></SelectOption>
</div>
);
}
export default Home;