1 引言

在上一篇文章《给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(上)》中笔者介绍了如何实现在Markdown渲染网页中加一个目录组件。不过,上一篇文章中只是介绍了一下功能也就是JavaScript部分的具体实现。其实要实现这个功能,另外一个关键就是样式也就是CSS部分的设计。

阅读本文可能需要的前置文章:

2 基础

在前端三剑客(HTML+CSS+JavaScript)中笔者认为CSS是最麻烦的,具体就麻烦在CSS并不是一个遵循程序员思维的语言,反而它遵循的是设计师的思维:要求对排版规则的掌控,要求能熟能生巧的积累,最好还要有一点美术审美。大部分的计算机语言都相通,但是CSS却特立独行。

另外,笔者觉得市面上关于CSS的中文教程也大多不太行,很多都是CSS属性的堆砌。笔者记得学习过一本CSS教程,光是开头介绍字体的设置就讲了一个很大的章节,这真的很难让初学者入门。有的人说CSS的属性要靠记忆,这是文科的思维,有一点道理在里面;但是笔者认为CSS作为一门计算机语言,还是有一些理性思维在里面的。其中,最理性最程序员思维的部分就是网页布局:设计出来的页面中的元素,至少要听你指挥,出现在你想要放置的位置。

2.1 盒子模型

盒子模型是网页布局中最基础的概念,定义了如何处理单个HTML元素。具体来说,就是每一个HTML元素都被看作是一个矩形的盒子,这个盒子由内容(content)、内边距(padding)、边框(border)和外边距(margin)组成。这个概念应该来说属于老生常谈了,基本上每个介绍CSS的教程都会首先介绍它。其实要掌握这个概念不用费那么多精力,对着浏览器后台开发工具调试一下padding、border和margin属性,看看每个参数的效果就可以了。如下图所示:

2.2 HTML文档流

HTML文档流(Document Flow)也是网页布局最基础概念之一,指的是HTML页面中的元素,默认按照从上到下、从左到右的顺序对页面元素进行排列和渲染的方式。这个理论听起来非常自然,甚至有点像废话文学;但确实就是网页布局的关键所在:

  1. HTML页面中的元素如果没有进行特定的排版设置,那么从上到下、从左到右的顺序就是HTML元素的默认位置,这让我们确定了通过CSS调整样式的起始值。
  2. HTML页面中元素大部分处于文档流中,即使通过CSS调整了位置,也只是改变了局部的布局方式,大体上仍然遵循这个规则。
  3. 大多数情况下HTML页面中的元素最好不要脱离文档流,因为脱离文档流往往需要比较精细的控制,除非少部分需求真的需要这么做。

在文档流中,HTML元素可以分为三种类型:

类型 特点 示例标签
块级元素 独占一行,自动换行,可设置宽高 div, p, h1h6
行内元素 不独占一行,宽度由内容决定,不能设置宽高 span, a, strong
行内块元素 行内显示,但可以设置宽高 display: inline-block

2.3 现代布局方式

与网页布局最直接相关的属性就是display,最基础的几种属性值如下:

  1. display: none; 元素不会被显示,也不占据任何空间,常用于隐藏元素。
  2. display: block; 元素作为块级元素显示(独占一行),可以设置宽度、高度、内外边距。前面介绍的块级元素display的默认属性值就是block
  3. display: inline;元素作为内联元素显示(和其他内联元素在同一行),不能设置宽度和高度。前面介绍的行内元素display的默认属性值就是inline
  4. display: inline-block;结合了inlineblock的特性,可以在同一行显示,并且可以设置宽度、高度、内外边距。常用于横向排列的导航栏或按钮组。

对于现代网页设计来说,更为重要的是display: flex;display: grid;这两种属性值。其中display: flex;更为重要一点,也就是所谓的弹性盒子布局(Flexbox)。

3 实现

3.1 弹性盒子布局

那么接下来就结合本文的具体实例来讲解Flexbox布局。回到本例的index.html:

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head> <body>
<div id="app">
<div id="post-article-placeholder"></div>
<div id="article-toc-placeholder"></div>
</div>
<script type="module" src="/src/main.js"></script>
</body> </html>

对应的样式文件style.css是:

body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
} #app {
/* 将容器设置为弹性容器。 */
display: flex;
flex-direction: row;
justify-content: center;
align-items: start;
gap: 1rem;
margin-top: 1rem;
width: 100%;
} #post-article-placeholder {
min-width: 600px;
max-width: 800px;
flex: 1 1 auto; /* 允许伸缩 */
} #article-toc-placeholder {
width: 260px;
position: sticky;
top: 0;
flex: 0 0 auto; /* 固定宽度不伸缩 */
}

在这里,我们想让app元素(包含博文内容控件和博文目录控件)居中显示,就设置根元素body为Flexbox布局:

body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

这里的样式设置的意思是:

  1. flex-direction定义主轴方向,设置成column表示body中的元素像列一样布局,也就是垂直布局。
  2. justify-content定义项目在主轴上的对齐方式,设置成center表示垂直方向上居中对齐。
  3. align-items定义项目在交叉轴上的对齐方式,设置成center表示在水平方向上居中显示。

app元素设置成居中之后,解下来我们想让app元素的子元素博文内容控件和博文目录控件左右布局,并且顶端对齐。要实现这个功能:当然还是要设置成Flexbox布局:

#app {
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}

而这里的样式设置的意思是:

  1. flex-direction: row;表示app中的元素像行一样布局,也就是左右水平布局。
  2. justify-content: center;表示app中的元素在水平方向上向中心靠齐。
  3. align-items: flex-start表示app中的元素在垂直方向上,沿着起始位置靠齐,也就是顶端对齐。

可以调整一下这些属性的值来加深对布局的理解。比如align-items的值设置成center,那么博文目录控件就会与博文内容控件在垂直居中对齐;设置成flex-end博文目录控件则会与博文内容控件底部对齐。justify-content的设置在某些情况下非常有用:比如需要在水平方向上(这里的主轴方向)靠左对齐,就设置成flex-start;需要靠右对齐,就设置成flex-end;需要两端对齐,就设置成space-between

Flexbox布局的好处就在这里,它可以通过设置垂直布局和水平布局,形成了一种对网页布局的通解:只要你拆分的盒子数量和层级够多,那么网页元素可以始终控制在HTML文档流中。换句话说,一个前端程序员的基本素质就是将原型转换成多层级的包含垂直/水平布局的盒子。比如说稀土掘金网站的首页,根据其布局我们可以进行如下拆分:

3.2 响应式布局

使用Flexbox布局还有一个好处,那就是可以实现响应式布局。所谓响应式布局,指的是使网页能够在不同的分辨率下都有比较好的浏览体验。结合本文的例子来说:

#app {
display: flex;
} #post-article-placeholder {
flex: 1 1 auto; /* 允许伸缩 */
min-width: 600px;
max-width: 800px;
} #article-toc-placeholder {
width: 260px;
flex: 0 0 auto; /* 固定宽度不伸缩 */
}

这里的flex 包含了三个子属性:

flex: <flex-grow> <flex-shrink> <flex-basis>;

具体的含义是:

子属性 描述
flex-grow 定义项目的放大比例,默认为 0(不放大)
flex-shrink 定义项目的缩小比例,默认为 1(可缩小)
flex-basis 定义项目在分配多余空间之前的初始大小,可以是长度值(如 200px)、百分比(如 auto)等

对于博文内容控件,flex: 1 1 auto;的意思是:

  • flex-grow: 1:该元素会尽可能地占据剩余空间。
  • flex-shrink: 1:该元素在空间不足时可以被压缩。
  • flex-basis: auto:该元素的初始宽度由其内容或显式设置的 width 决定。

对于博文目录控件,flex: 0 0 auto;的意思是:

  • flex-grow: 0:该元素不会扩展。
  • flex-shrink: 0:该元素不会压缩。
  • flex-basis: auto:项目的初始大小由其内容或显式设置的 width 决定。

也就是说,博文目录的宽度保持原有大小不参与伸缩;而博文内容控件则会根据容器空间进行伸缩。这也是这种布局方式被称为弹性盒子布局的原因,在这种布局方式下,页面中的元素可以灵活响应页面分辨率宽高的变化。不过,在现代网页中文档内容区域的宽度范围通常在600px800px之间,这里就设置博文内容控件的最小宽度为600px,最大宽度为800px

当然响应式布局的内涵不止这一点,响应式布局最终希望在不同设备(如桌面电脑、平板电脑和手机)上都有最佳的浏览体验,这需要自动根据屏幕尺寸和方向自动调整布局结构、元素大小及位置;要实现这一点还是离不开Flexbox布局。

3.3 粘性定位

在这里,笔者就回答一下上一篇文章《给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(上)》中的问题:博文目录是如何始终保证粘在页面的右上角的?因为使用了粘性定位:

#article-toc-placeholder {
position: sticky;
top: 0;
}

这里的意思就是当博文目录控件离开视图页面的时候,就将博文目录控件的位置粘在页面的顶部(top属性值为0的位置)。看起来这个实现非常简单,但是如果在一些复杂的情况下使用会有一些问题,因为粘性定位本质上是脱离了HTML文档流的。比如说,将博文目录粘在右侧控件的顶部还好,如果是粘在其他特定的位置,就需要精确地控制位置属性,否则就很容易与其他页面元素冲突。

不止粘性定位,浮动(float)、绝对定位(position: absolute)、固定定位(position: fixed)都会脱离HTML文档流,不推荐用来作为主要的布局方式,这里就不细说了。

4 结语

最终的博文目录粘在页面右上角的效果如下所示:

给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(下)的更多相关文章

  1. 在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world

    一.题目 编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world.内核版本要求2.6.18 二.实验环境 物理主机:w ...

  2. JS数组 团里添加新成员(向数组增加一个新元素)只需使用下一个未用的索引,任何时刻可以不断向数组增加新元素。myarray[5]=88;

    团里添加新成员(向数组增加一个新元素) 上一节中,我们使用myarray变量存储了5个人的成绩,现在多出一个人的成绩,如何存储呢?  只需使用下一个未用的索引,任何时刻可以不断向数组增加新元素. my ...

  3. 删除一个目录和其各级子目录下的.svn文件

    两种方法[1]用find命令和其action来实现[2]用rm直接实现$ cd /tmp/xxx$ rm -rf  `find . -name .svn`就可以实现了. 删除SVN目录及从服务器端删除 ...

  4. Vertica增加一个数据存储的目录

    Vertica增加一个数据存储的目录 操作语法为: ADD_LOCATION ( 'path' , [ 'node' , 'usage', 'location_label' ] ) 各节点添加目录,并 ...

  5. echarts之简单的入门——【二】再增加一个柱状图和图例组件

    echarts之简单的入门——[一]做个带时间轴的柱状统计图 现在需求说,我需要知道日答题总次数和活跃人数,那么我们如何在上面的图表中增加一个柱状图呢? 如果你看过简单入门中的配置项手册中series ...

  6. CAD在网页中增加一个射线

    主要用到函数说明: IMxDrawBlockTableRecord::AddRay 向记录中增加一个射线,详细说明如下: 参数 说明 point1 射线上的点1 point2 射线上的点2 js代码实 ...

  7. CAD增加一个有形的线型(网页版)

    主要用到函数说明: _DMxDrawX::AddTextStyle1 向数据库中增加一个文字样式.详细说明如下: 参数 说明 BSTR pszName 文字样式名称 BSTR pszFileName ...

  8. 【万字长文】从零配置一个vue组件库

    简介 本文会从零开始配置一个monorepo类型的组件库,包括规范化配置.打包配置.组件库文档配置及开发一些提升效率的脚本等,monorepo 不熟悉的话这里一句话介绍一下,就是在一个git仓库里包含 ...

  9. 基于.NetCore开发博客项目 StarBlog - (19) Markdown渲染方案探索

    前言 笔者认为,一个博客网站,最核心的是阅读体验. 在开发StarBlog的过程中,最耗时的恰恰也是文章的展示部分功能. 最开始还没研究出来如何很好的使用后端渲染,所以只能先用Editor.md组件做 ...

  10. 发布自己第一个npm 组件包(基于Vue的文字跑马灯组件)

    一.前言 总结下最近工作上在移动端实现的一个跑马灯效果,最终效果如下: 印象中好像HTML标签的'marquee'的直接可以实现这个效果,不过 HTML标准中已经废弃了'marquee'标签 既然HT ...

随机推荐

  1. Linux中查看进程状态信息

    一.常用命令总结  ps -l   列出与本次登录有关的进程信息:    ps -aux   查询内存中进程信息:    ps -aux | grep ***   查询***进程的详细信息:    t ...

  2. BUUCTF---keyboard

    题目 ooo yyy ii w uuu ee uuuu yyy uuuu y w uuu i i rr w i i rr rrr uuuu rrr uuuu t ii uuuu i w u rrr e ...

  3. Netty源码—7.ByteBuf原理二

    大纲 9.Netty的内存规格 10.缓存数据结构 11.命中缓存的分配流程 12.Netty里有关内存分配的重要概念 13.Page级别的内存分配 14.SubPage级别的内存分配 15.Byte ...

  4. ajax 多次请求相同链接 相同参数,缓存问题

    经常会发现,ajax 多次调用同一个接口时(get),参数不变. 为了提升性能,浏览器就不会和服务器进行交互,获取到的数据 就不会发生变化 解决方案:添加随机参数.或者时间戳 类似在接口后面 添加 D ...

  5. protected修饰符讲解、java中继承的特点-java se进阶 day01

    1.protected权限修饰符的介绍 之前在说权限修饰符时候,没有细说protected,今天,我们就来聊聊protected 如图,protected修饰符中,"不同包的子类" ...

  6. Linux浅谈(四)----中断&异常

    简介 CPU中断时操作系统中的两个重要概念,都作用于改变CPU执行的正常流程. 当出现某些特殊情况(如外部设备请求服务.定时器时间到等)时,CPU 暂停当前正在执行的程序,转去执行处理这些特殊情况的程 ...

  7. JavaScript Library – Embla Carousel

    前言 2022 年 4 月,我写了一篇 Swiper 介绍. Swiper 是当时前端最多人使用的 Slider 库,没有之一,一骑绝尘. 但是!时过境迁,这两年已经有一匹神秘的黑马悄悄杀上来了. 它 ...

  8. 【FAQ】HarmonyOS SDK 闭源开放能力 — IAP Kit(6)

    1.问题描述: 支付场景,表现是在沙盒情况下所有商品都可以正常跑通,但是在非沙盒情况下,线上购买年包1800大额支付华为的 iap.createPurchas 在输完密码就会报 1001860001 ...

  9. web自动化基础

    一.浏览器驱动 下载浏览器对应版本驱动 Chromedriver下载地址:https://npm.taobao.org/mirrors/chromedriver 下载谷歌对应版本对应系统的驱动,把下载 ...

  10. 2025dsfz集训Day7: KMP与Trie树

    Day7: KMP与Trie树 \[Designed\ By\ FrankWkd\ -\ Luogu@Lwj54joy,uid=845400 \] 特别感谢 此次课的主讲 - Kwling KMP算法 ...