React-仿微信通讯录控件

起因:产品给了一个需求,要求写一个跟微信朋友圈一样,取首字母产生的通讯录标签页,

因为很多人要求源码给上
现在贴上源码
源码地址

写了一遍,发现意外的好写

最终效果

数据采用了随机字符串的方法,并且使用数组方法排序,

        // 去除数字key
        let regExp = new RegExp(/^[0-9]$/);
        let data = [];
        let title = [];
        for (let i = 0; i < 200; i++) {
            let s = Math.random().toString(36).substr(2);
            let subStr = s.substr(0, 1);
            if (regExp.test(subStr)) {
                i--;
                continue;
            }
            if (data.indexOf(subStr) === -1) {
                data.push(subStr);
            }
            data.push(s);
            let number = title.indexOf(subStr);
            if (number === -1) {
                title.push(s.substr(0, 1));
            }
        }
        data.sort();
        title.sort();

核心方法就是根据找到其中的索引,跳转位置,通过window.scrollTo,进行跳转,用findIndex来进行查找准确标签位置,进行跳转,

主要计算公式:

需要跳转的高度 = ( 条目高度  *  索引位置 [+ 其他控件的高度,在其之上的控件])

注: 每个条目大小必须已知 

                {
                    this.state.title.map((item, position) => {
                        return <div
                            className={item === this.state.currentTitle ? "select-title" : ""}
                            onClick={() => {
                                let find = this.state.data.findIndex(obj => obj === item);
                                window.scrollTo(0, find * 100 - position * 100 + position * 48 + 358);
                            }}>{item.toLocaleUpperCase()}</div>
                    })
                }

添加滚动监听器 


    componentWillUnmount() {
        window.removeEventListener('scroll', this.onScrollChange, false)
    }


    componentDidMount() {
        window.addEventListener('scroll', this.onScrollChange, false)
    }

滚动计算方式 

首先获取滚动距离 ,其次先定义一个起始位置,然后获取位置距离,由于字母key和内容item的高度不一致,所以需要获取到两者之间的item,所以获取的是字母key和下一个字母key的item数量.由length记录,去记录item的高度.其次获取到key的数量*key的高度.如果滚动距离小于此高度,则说明,目前在的位置是当前key.

注: item和key的高度需要已知


    onScrollChange = (e) => {
        // console.log();
        let scroll = window.scrollY [- 顶部控件高度];
        let current = "a";
        let {data, title} = this.state;
        let length = 0;
        for (let i = 0; i < title.length; i++) {
            let currentTitle = title[i];
            if (i === title.length - 1) {
                current = currentTitle;
                break;
            }
            let nextTitle = title[i + 1];
            let currentIndex = data.indexOf(currentTitle);
            let nextIndex = data.indexOf(nextTitle);
            let sum = nextIndex - (currentIndex + 1);
            length = length + sum * 100;
            let number = length + (i + 1) * 48;
            if (scroll < number) {
                current = currentTitle;
                break;
            }
        }
        this.setState({currentTitle: current})
    };

如果导致滚动监听不到的情况,

一般为overflow:auto之类的情况,如果父容器设置了,可能导致不成功

 

源码,仅供参考

import React from 'react'
import BaseComponent from "../../../../page/base/BaseComponent";
import Grid from "../grid/Grid";
import {connect} from "react-redux";
import {withRouter} from "react-router";
import * as PATH from "../../../../conts/path";

const regExp = new RegExp(/^[0-9]\d*\w*$/);

class BrandItem1 extends BaseComponent {


    constructor(props) {
        super(props);

        this.state = {
            data: [],
            title: [],
            header: "brand",
            currentTitle: "a"
        }
    }


    onScrollChange = (e) => {
        let scroll = window.scrollY - 358;
        let current = this.state.title[0];
        let {data, title} = this.state;
        let length = 0;
        for (let i = 0; i < title.length; i++) {
            let currentTitle = title[i];
            if (i === title.length - 1) {
                current = currentTitle;
                break;
            }
            let nextTitle = title[i + 1];
            let currentIndex = data.indexOf(currentTitle);
            let nextIndex = data.indexOf(nextTitle);
            let sum = nextIndex - (currentIndex + 1);
            length = length + sum * 100;
            let number = length + (i + 1) * 48;
            if (scroll < number) {
                current = currentTitle;
                break;
            }
        }
        this.setState({currentTitle: current})
    };


    componentDidMount() {
        window.addEventListener('scroll', this.onScrollChange);


    }

    componentWillMount() {
        this.setData(this.props)
    }

    componentWillReceiveProps(nextProps) {
        this.setData(nextProps);
    }

    setData(props) {
        const {brandSource} = props;
        let data = [];
        let title = [];
        let data2 = [];
        brandSource.forEach(item => {
            const s = item.brandName;
            let subStr = s.substr(0, 1);
            if (regExp.test(subStr)) {
                // 匹配上数字开头
                data2.push(s);
            } else {
                if (data.indexOf(subStr) === -1) {
                    //传入标题
                    data.push(subStr);
                }
                data.push(s);
                let number = title.indexOf(subStr);
                if (number === -1) {
                    title.push(s.substr(0, 1));
                }
            }
        });
        data.sort();
        title.sort();
        if (data2.length !== 0) {
            data.push("#");
            data2.forEach((item, index) => {
                data.push(item)
            });
            title.push("#");
        }
        this.setState({title, data}, () => {
            this.onScrollChange(null);
        });
    }

    componentWillUnmount() {
        window.removeEventListener('scroll', this.onScrollChange)
    }

    render() {
        const {title, data} = this.state;
        let innerHeight = window.innerHeight;
        let top = innerHeight * 0.1;
        let number = innerHeight * 0.8 / title.length;
        return <React.Fragment>
            <div className={"item1"}>
                <div>
                    <Grid dataSource={this.props.dataSource} itemStyle={{margin: "0 19px 0"}}/>
                </div>
                <div>
                    {
                        data.map((item, position) => {
                            let find = title.find(obj => item === obj);
                            if (find) {
                                return <div className={"padding-left-30 list-item-1  kalinga-font"}
                                >{item.toLocaleUpperCase()}</div>
                            }
                            return <div className={"padding-left-30 list-item-2  kalinga-font"} onClick={(e) => {
                                const brand = this.props.brandSource.find(n => n.brandName === item);
                                this.startPage(PATH.BRAND +"?id=" + brand.id, false);
                            }}
                            >{item}</div>
                        })
                    }
                </div>

                <div className={"title"} style={{top: top, height: innerHeight * 0.8}}>
                    {
                        title.map((item, position) => {
                            return <div
                                style={{height: number, lineHeight: number + "px"}}
                                className={item === this.state.currentTitle ? "select-title" : ""}
                                onClick={() => {
                                    let find = data.findIndex(obj => obj === item);
                                    setTimeout(() => {
                                        window.scrollTo(0, find * 100 - position * 100 + position * 48 + 358);
                                    }, 10);
                                }}>
                                <div
                                    className={item === this.state.currentTitle ? "select-title-span" : ""}>{item.toLocaleUpperCase()}</div>
                            </div>
                        })
                    }
                </div>
            </div>
        </React.Fragment>
    }

}


const mapStateToProps = state => {
    const {pageInfo, historyPage} = state;
    return {pageInfo, historyPage};
};

export default withRouter(connect(mapStateToProps)(BrandItem1));

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值