自定义响应式布局逻辑 20210701

自定义响应式布局逻辑

欢迎各位交流分享

1 目标
单行时,框体保持一定间距:
单行时,框体保持一定间距
双行或者多行时,每列的宽度以最大宽的元素的宽度为准 :
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2 方案
    2.1 方案一:Js + Css + Flex布局封装组件完成
    经过可行性分析后,发现控制起来不是特别容易,需要每行的每个元素设定宽度,成本有点高。
    2.2 方案二:Css + Grid布局封装组件完成
    并没有找到,列数自适应的方案,不过倒是了解到有栅格化布局,以及UX设计师的职业,很感兴趣。

    猜想大概率是浏览器没想要支持这类操作,因为性能方面不是很优异。需要涉及不确定性的动态分配,
    2.3 方案三:Js + Css + Grid布局封装组件完成
    在方案一的基础上,减少实施难度得到的方案,如果替换成方案一的实施方案,也可以,思路是一样的,只不过grid更方便。

未进行组件封装,如果组件化,可能需要限制onresize的绑定方式配合,以下代码实现

<!DOCTYPE html>
<html>

<head>
    <style>
        .box {
            display: flex;
            flex-wrap: wrap;
        }

        .box>.item {
            width: 150px;
            height: 50px;
            margin-top: 8px;
            margin-right: 16px;
            border: 1px solid red;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .box>.item:nth-child(2n+1) {
            width: 100px;
        }

        .box>.item:nth-child(3n+1) {
            width: 80px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div ref="box" class="box" :style="styleOfBox">
            <div class="item">1</div>
            <div class="item">2</div>
            <div class="item">3</div>
            <div class="item">4</div>
            <div class="item">5</div>
            <div class="item" style="width: 550px">6</div>
            <div class="item">7</div>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data() {
            return {
                isReized: true
            };
        },
        mounted() {
            console.log("init");
            // 监听窗口变化,当窗口变化时,通过计算属性清空grid式样
            window.onresize = () => {
                console.log("resize");
                this.isReized = true;
            }
            // 页面初次触发根据flex布局的计算,生成grid式样
            this.isReized = false;
        },
        computed: {
            // isReized, true时清空grid布局,false时根据flex布局计算并生成grid式样
            styleOfBox() {
                console.log("computed");
                // 重新加载时清空grid布局
                if (this.isReized) {
                    return {};
                }
                let box = this.$refs.box;
                let children = [...box.children];
                if (children.length > 0) {
                    // 计算‘最大列数’,因为之前写好了,所以暂时继续使用flex布局下对象的坐标检测行边缘,得到最大列数
                    // 可以考虑使用‘元素宽度’与‘外框宽度’比较,得到最大列数
                    let topOfFirst = box.firstChild.offsetTop;
                    let maxColumnCount = children.findIndex(x => topOfFirst != x.offsetTop);

                    // 单行时,不进行grid布局切换
                    if (maxColumnCount == -1) {
                        return {};
                    }

                    let columnsWidth = new Array();
                    let boxWidth = this.getInnerWidth(box); // 获取边框内部可用宽度
                    let self = this;
                    // 涉及动态分配算法,不如提前进行设计上的合理规避,最终结果还不一定好看
                    // 对算法精通的大神,如果有兴趣,给小弟分享一下其他计算方案
                    // 计算合理可用的最大列数,以及对应的列宽,如果仅剩余一列,使用那一列的最大值作列宽
                    (function calculate(columnCount) {
                        // 暂时获取首行做列宽
                        columnsWidth = children.slice(0, columnCount).map(x=>self.getOffsetWidth(x));
                        // 计算最大满行数
                        let baseCount = parseInt(children.length / columnCount);
                        // 计算目前总行宽
                        let widthTotal = columnsWidth.reduce((total, x) => x + total, 0);
                        // 计算当前列数是否能满足要求
                        let isFail = columnsWidth.some((x, i) => {
                            // 遍历对应列的单元格,从第二行开始
                            for (let j = 1; j <= baseCount; j++) {
                                let currI = j * columnCount + i;
                                // 排除最后一行越界的可能性
                                if (currI < children.length) {
                                    // 获取当前元素宽度
                                    let currW = self.getOffsetWidth(children[currI]);
                                    // 当前元素宽度大于当前列宽,则进行列宽修改,并计算合理性
                                    if (currW > x) {
                                        let updatedTotal = widthTotal - x + currW;
                                        // 如果修改后总行宽不大于元素宽度则正式替换列宽
                                        // 如果仅剩余一列,使用那一列的最大值作列宽
                                        if (updatedTotal <= boxWidth || columnCount == 1) {
                                            widthTotal = updatedTotal;
                                            columnsWidth[i] = currW;
                                        } else {
                                            // 如果修改后总行宽大于元素宽度则此方案不可行
                                            return true;
                                        }
                                    }
                                }
                            }

                            // 能走到这里,说明此方案可用
                            return false;
                        });

                        // 不满足要求时,重新减少一列进行重新计算,直到满足为止
                        isFail && calculate(columnCount - 1);
                    })(maxColumnCount);

                    return {
                        display: "grid",
                        gridTemplateColumns: columnsWidth.map(x => x + "px").join(" ")
                    };
                }

                return {};
            }
        },        
        updated() {
            console.log("updated");
            // 清空grid式样成功后,触发根据flex布局的计算,并生成新的grid式样
            if (this.isReized) {
                this.isReized = false;
            }
        },
        methods: {
            // 获取元素实际高度,包括margin
            getOffsetWidth(el) {
                let computedStyles = window.getComputedStyle(el);
                let left = parseInt(computedStyles["margin-left"].slice(0, -2));
                let right = parseInt(computedStyles["margin-right"].slice(0, -2));
                return left + el.offsetWidth + right;
            },
            // 获取元素内部可用宽度,抛去边框和内边距之后
            getInnerWidth(el) {
                let computedStyles = window.getComputedStyle(el);
                let leftP = parseInt(computedStyles["padding-left"].slice(0, -2));
                let rightP = parseInt(computedStyles["padding-right"].slice(0, -2));
                let leftB = parseInt(computedStyles["border-left"].slice(0, -2));
                let rightB = parseInt(computedStyles["border-right"].slice(0, -2));
                return leftP + leftB + el.offsetWidth + rightB + rightP;
            }
        },
    });
</script>

</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值