先看效果
收起时
展开后
主要结构
我用的 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
循环所有标签(.userLabel), 看是否有多行, 通过所有节点的 left 去判断, 如果 left 相同的有多个, 就证明有多行
获取标签父级 (#labelBox) 的宽度 width
获取到按钮 (#moreLabel) 的宽度
过滤第一行节点的 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();
- }
- })
- }
做下记录
来源: http://www.jianshu.com/p/87f3c14038a6