阅读量:10
目录
一、效果图
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;