小程序标签展开收起功能实现

先看效果

收起时

展开后

主要结构

我用的mpvue,如用原生标签直接转换成原生即可

<div id="labelBox">
    <div class="label userLabel" v-for="(label,inx) in labelList" :key="inx">{{label}}</div>
    <div class="more" v-show="showLabel===1" id="moreLabel" @click="openMore">
        <div>全部{{allLabel.length}}个</div>
        <img class="icon ml5" src="/static/img/i_label_down.png" />
    </div>
    <div class="more" v-show="showLabel===2" @click="closeMore">
        <div>收起</div>
        <img class="icon ml5" src="/static/img/i_label_up.png" />
    </div>
</div>
export default {
    data() {
        return {        
            labelList: [],  // 视图显示的标签集合
            allLabel: [],   // 所有的标签集合
            firstLabel: [], // 默认显示的标签集合
            showLabel: 1,   // 0 两个按钮都不显示,1 显示展开,2 显示收起
        }
    },
    ...
}

思路

利用小程序api NodesRef.boundingClientRect 获取节点的位置与大小信息,主要用到 width,left,right

  1. 循环所有标签(.userLabel),看是否有多行,通过所有节点的 left 去判断,如果 left 相同的有多个,就证明有多行
  2. 获取标签父级(#labelBox)的宽度 width
  3. 获取到按钮(#moreLabel)的宽度
  4. 过滤第一行节点的 right,如果与按钮的width相加小于等于父级盒子的width就保留

具体的代码

wxp为微信接口Promise化,会在之后列出用到的

export default {
    data() {
        return {        
            labelList: [],  // 视图显示的标签集合
            allLabel: [],   // 所有的标签集合
            firstLabel: [], // 默认显示的标签集合
            showLabel: 1,   // 0 两个按钮都不显示,1 显示展开,2 显示收起
        }
    },
    methods: {
        async loadPageData(){
            // 请求后台数据
            const res = ...

            // 设置
            this.allLabel = res.labes;          // 记录所有的标签
            this.labelList = this.allLabel;     // 先插入所有表情
            // 设置状态
            if(this.allLabel.length>0){
                await wxp.timeout(300); // 插入视图之后不会马上获取到节点信息,延迟获取
                this.setLabelStauts();
            }
            
        },

        // 设置标签状态
        async setLabelStauts(){
            const boxDom = await wxp.getElementById('#labelBox');
            const labelDoms = await wxp.getElementsByClassName('.userLabel');
            const btnDom = await wxp.getElementById('#moreLabel');
            const left = labelDoms[0].left;

            // 分行转为二维数组
            let lineArr = [];       
            let lineIndex = -1;
            labelDoms.forEach(v => {
                if(v.left==left){
                    lineIndex++;
                    lineArr[lineIndex] = [];
                }
                lineArr[lineIndex].push(v);
            })

            // 超过一行
            if(lineArr.length>1){
                // 默认显示加载更多按钮
                this.showLabel = 1;
                const firstTr = lineArr[0].filter(v => (v.right+btnDom.width+(left/15*15)) <= boxDom.width);
                this.firstLabel = this.allLabel.slice(0,firstTr.length);
                this.labelList = this.firstLabel;
            }else{
                this.showLabel = 0;
            }
        },

        // 展开
        openMore(){
            this.showLabel = 2;
            this.labelList = this.allLabel;
        },
        // 收起
        closeMore(){
            this.showLabel = 1;
            this.labelList = this.firstLabel;
        }
    }
}

wxp.js相关代码

/**
 * 延时
 * @param {*} delay 
 */
export const timeout = delay => new Promise(resolve => setTimeout(resolve, delay));

/**
 * 根据ID获取dom的盒模型信息
 * @param {*} id 
 */
export const getElementById = (id='') => {
    return new Promise((resolve, reject) => {
        if ((typeof id).toLowerCase() !=='string'){
            const err = {
                errMsg: '请输入字符串,例如 #box'
            }
            reject(error(err.errMsg,err));
        } else if (id.indexOf('#') < 0) {
            const err = {
                errMsg: '请输入ID,例如 #box'
            }
            reject(error(err.errMsg,err));
        }else{
            var query = wx.createSelectorQuery()
            query.select(id).boundingClientRect();
            query.selectViewport().scrollOffset();
            query.exec(rect => {
                if (rect[0]){
                    let info = rect[0];
                    info.position = {
                        left: rect[1].scrollLeft + info.left,
                        top: rect[1].scrollTop + info.top
                    };
                    resolve(info);
                }else{
                    const err = {
                        errMsg: '没有获取到信息'
                    }
                    reject(error(err.errMsg,err));
                }
            })
        }
    })
}

/**
 * 根据类名获取dom信息
 * @param {*} className 
 */
export const getElementsByClassName = (className = '') => {
    return new Promise((resolve, reject) => {
        if ((typeof className).toLowerCase() !== 'string') {
            const err = {
                errMsg: '请输入字符串,例如 .box'
            }
            reject(error(err.errMsg,err));
        } else if (className.indexOf('.') < 0) {
            const err = {
                errMsg: '请输入类名,例如 .box'
            }
            reject(error(err.errMsg,err));
        } else {
            wx.createSelectorQuery().selectAll(className).boundingClientRect(rects => {
                resolve(rects);
            }).exec();
        }
    })
}
做下记录

推荐阅读更多精彩内容