supce's blog

CSS Secret 读书笔记之结构与布局(二)


根据兄弟元素的数量设置样式

有时候会碰见这样一种场景:当一个列表不断延长时,通过隐藏空间或压缩控件等方式来节省屏幕空间,用来提升用户体验。
这时候就需要我们根据兄弟元素的总数来为它们设置相应的样式。
设想一个列表,假设仅当列表的总数为4时才对这些列表项设置样式,这时可以使用li:nth-child(4)来选中列表的第四个列表项。但是我们的需求却是:
在列表的总数为4时选中每个列表项
如果直接利用兄弟选择符li:nth-child(4),li:hth-child(4)~li得到的是第四个列表以及它之后的所有列表项。那么我们转换一下思路分以下几种情况:
当只有一个列表项时,直接利用:only-child即:

li:only-child{
    /*只有一个列表时的样式*/
}

对于只有一个列表项的列表,其第一项同时也是该列表的最后一项。也就是说:only-child等效于:first-child:last-child,而:last-childnth-last-child(1)的简写,所以:

li:first-child:nth-last-child(1){
    /*等效于:only-child*/
}

tips :nth-last-child(n) 选择器匹配属于其元素的第 N 个子元素的每个元素,不论元素的类型,从最后一个子元素开始计数。

由上面可以得出对于一个正好包含4项的列表,如果想命中所有列表项,可以这样写:

li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4)~li{
    /*列表包含4项且命中所有项*/
}

根据参考手册:nth-last-child()选择器的参数不仅仅可以是简单的数字,也可以是类似于an+b的表达式,其中,n 是计数器(从 0 开始),b 是偏移值。
比如,我们指定了下标是 3 的倍数的所有p元素的背景色,从最后一个子元素开始计数:

p:nth-last-child(3n+0){
    background:#ff0000;
}

而对于n+b(相当于a=1)这个公式,无论n如何取值,这个表达式都无法产生一个小于b的值,可以选中从第b个开始的所有子元素。
因此:

li:first-child:nth-last-child(n+4),
li:first-child:nth-last-child(n+4)~li{
    /*列表至少包含4项且命中所有项*/
}

同理,-n+b这种形式的表达式可以选中开头的b个元素。因此仅当列表中有4个或更少的列表时,可以这样写:

li:first-child:nth-last-child(-n+4),
li:first-child:nth-last-child(-n+4)~li{
   /*列表最多包含4项且命中所有项*/
}

如果想命中包含2-6个列表项的列表时,只需把上面两种技巧组合一下:

li:first-child:nth-last-child(n+2):nth-last-child(-n+6),
li:first-child:nth-last-child(n+2):nth-last-child(-n+6)~li{
    /*当列表包含2-6项时,命中列表中所有列表项*/
}

利用上面的技巧可以写一个类似于变色便签的小例子:
HTML:

<ul class="palette">
    <li>
        <div class="color-options">
            <a class="add" href="#">Add</a>
            <a class="delete" href="#">Delete</a>
        </div>
        <div class="input-group">
            <textarea rows="12" class="form-control">write someting...</textarea>
        </div>
    </li>
</ul>

CSS:

<style type="text/css">
    ul{
        list-style: none;
    }
    .palette{
        display: flex;
        height: 300px;
        max-width: 900px;
        font: bold 90%/1 sans-serif;
    }
    .palette li{
        flex: 1;
        background: #D6E055;
    }
    .color-options{
        margin: 0 10px;
        padding: 10px;
        background: rgba(0,0,0,.5);
        border-radius: 0 0 15px 15px;
        overflow: hidden;
    }
    .color-options a{
        color: white;
        text-decoration: none;
    }
    .color-options a:before{
        display: inline-block;
        font-size: 1rem;
        width: 1.2rem;
        margin-right: .3rem;
        line-height: 1.2;
        background: white;
        border-radius: 50%;
        text-align: center;
    }
    .color-options .add:before {
        content:'✚';
        color:#590;
    }
    .color-options .delete:before {
        content:'✖';
        color:#b00;
    }

    .color-options a:after {
        content: ' color';
        font-weight: normal;
    }
    .add { float:left; }
    .delete { float: right; }

    .palette li:only-child .delete{
        display: none;
    }
    .palette li:first-child:nth-last-child(n+4) .color-options a:after,
    .palette li:first-child:nth-last-child(n+4) ~ li .color-options a:after{
        content: none;
    }
    .palette li:first-child:nth-last-child(n+4) .color-options a,
    .palette li:first-child:nth-last-child(n+4) ~ li .color-options a{
        font-size: 0;
        color: transparent;
    }
    .input-group{
        margin:20px;
    }
    .form-control{
        width: 100%;
        padding: 10px;
        font: 120%/1 sans-serif;
        box-sizing:border-box; /*防止溢出*/
        border-radius: 15px;
        outline: none;
        resize: none;
    }
</style>

JS:

<script type="text/javascript">
    var colors = [
        '#FEE169', '#CDD452', '#2E95A3', '#F9722E',
        '#E6E2AF', '#A7A37E', '#EFECCA', '#046380', 
        '#50B8B4', '#C6FFFA', '#E2FFA8', '#D6E055','#C9313D'
    ],
    palette = document.querySelector('.palette'),
    template = palette.firstElementChild;
    function addColor(template){
        var li = template.cloneNode(true);
        var color = colors.shift();
        colors.push(color);
        li.style.background = color;
        // palette.insertBefore(li,template.nextSibling);
        palette.appendChild(li);
    }
    palette.onclick = function(evt){
        var button = evt.target;
        if(button.className == 'add'){
            addColor(button.parentNode.parentNode)
        }else if(button.className == 'delete'){
            if(confirm('您确定删除?')){
                var li = button.parentNode.parentNode;
                li.parentNode.removeChild(li);
            }   
        }
    }
</script>


紧贴底部的页脚

有一个具有块级样式的页脚,当页面内容足够长时它一切正常,而当页面比较短时,就会出现问题。此时的问题在于页脚不能像我们期望中那样“紧贴”在视口的最底部,而是紧跟在内容的下方。

上图是将内容div删掉后的天津教育考试院官网

针对于这种情况可以用下面两种方式解决:

基于固定高度的解决方案

这种方案的主要思路是:假设页脚的文本不折行,然后估算出页脚实际所占的高度。然后再计算出页头的高度。最后借助视口相关的长度单位以及calc()函数,把页脚固定到底部。其核心代码为:

main{
    min-height:calc(100vh - 页头高度 - 页脚高度);
    /*避免内边距或边框扰乱高度的计算*/
    box-sizing:border-box;
}

举个例子,HTML:

<header>
    <h1>标题</h1>
</header>
<main>
    <p>
        空空道人看了一回,晓得这石头有些来历,遂向石头说道:“石兄,你这一段故事,据你自己说来,有些趣味,故镌写在此,意欲闻世传奇。据我看来:第一件,无朝代年纪可考;第二件,并无大贤大忠、理朝廷、治风俗的善政,其中只不过几个异样女子,或情或痴,或小才微善。我纵然抄去,也算不得一种奇书。”石头果然答道:“我师何必太痴!我想历来野史的朝代,无非假借汉、唐的名色;莫如我这石头所记不借此套,只按自己的事体情理,反倒新鲜别致。况且那野史中,或讪谤君相,或贬人妻女,奸淫凶恶,不可胜数;更有一种风月笔墨,其淫秽污臭最易坏人子弟。至于才子佳人等书,则又开口‘文君’,满篇‘子建’,千部一腔,千人一面,且终不能不涉淫滥。在作者不过要写出自己的两首情诗艳赋来,故假捏出男女二人名姓;又必旁添一小人拨乱其间,如戏中的小丑一般。更可厌者,‘之乎者也’,非理即文,大不近情,自相矛盾。竟不如我这半世亲见亲闻的几个女子,虽不敢说强似前代书中所有之人,但观其事迹原委,亦可消愁破闷;至于几首歪诗,也可以喷饭供酒。其间离合悲欢,兴衰际遇,俱是按迹循踪,不敢稍加穿凿,至失其真。
    </p> 
</main>
<footer>
    <p>© 2016 No rights reserved.</p>
    <p>Made with ♥ by supce.</p>
</footer>

CSS:

main {
    min-height: calc(100vh - 5em - 7em)
}
body {
    margin: 0;
    font: 100%/1.5 Palatino Linotype, Palatino, serif;
}
h1 { margin: .5em 0 0; }
header { text-align: center; height: 3em; }
main, footer {
    display: block;
    padding: .5em calc(50% - 400px);
}
footer {
    background: linear-gradient(#222, #444);
    color: white;
    height: 6em;
}

效果如下:

基于flex弹性布局的解决方案

之前有一篇关于弹性布局的日志
要想让页脚紧贴底部:

  • 首先对<body>设置display:flex;flex-flow:column;,保证body内的子元素为垂直排列。
  • 其次,设置<body>min-height属性为100vh,这样它就至少会占据整个视口的高度。
  • 最后,我们希望页头和页脚有其内部因素来决定,而内容区块的高度应该可以自动伸展并占满所有的可用空间。这就需要给<main>这个容器的flex属性指定一个大于0的值,我们可以暂时设置为1即可。

这种方案的核心代码如下:

body{
    display: flex;
    flex-flow: column;
    min-height: 100vh;
}
main{
    flex: 1;
}

国庆节偷个小懒,不举例子了