前端学习进度小记3

HTML表单的熟悉和理解

HTML 系统理论笔记:表单

为什么要单独给表单开一个笔记

表单是HTML向后台穿透数据的一个窗口,自然也就是绝大多数 Web 应用与网站与用户交互的核心:登录、注册、搜索、提交评论、支付、文件上传、调查问卷等都依赖表单。表单体验的质量直接影响转化率、用户满意度与安全性。所以理解表单非常的重要。


表单的基础结构与语义元素

<form action="/submit" method="post">
  <label for="name">姓名</label>
  <input id="name" name="name" type="text" />

  <button type="submit">提交</button>
</form>

form元素包裹起来的地方就是一个表单,当我们单机提交的时候,就会生成对应的URL回传给我们的后台服务器进行解析。自然,为了说明我们交的东西什么是什么,就要理解<form> 的重要属性:

  • action:提交到的 URL(可以是相对或绝对地址)。
  • methodgetpost(默认 get)。
  • enctype:编码类型,文件上传时为 multipart/form-data,默认 application/x-www-form-urlencoded
  • novalidate:禁用浏览器内置表单验证。
  • target:提交后打开方式(例如 _blank_self)。

语义上,使用 <label> 关联输入控件(通过 for 与 input 的 id)可以显著提升可访问性与点击体验。


常见表单控件详解

<input>(最通用)

<input> 有许多 type

  • textpasswordemailtelurlsearch:文本输入,email/url 会启用基本格式检查并在移动端唤起相应键盘。
  • numberrange:数值输入与范围滑块(注意 number 仍会以字符串提交)。
  • datedatetime-localtimemonthweek:日期/时间选择控件(浏览器原生控件,兼容性注意)。
  • color:颜色选择器。
  • checkbox:多选项开关。
  • radio:单选组(通过同 name 进行分组)。
  • file:上传文件,支持 multiple 属性以允许多文件选择。
  • hidden:隐藏字段,常用于携带状态或 token。
  • submitresetbutton:触发提交、重置或普通按钮。
<label>
  订阅
  <input type="checkbox" name="subscribe" value="yes">
</label>

<!-- 单选组 -->
<label><input type="radio" name="gender" value="m"></label>
<label><input type="radio" name="gender" value="f"></label>

<textarea>

多行文本输入:

<label for="message">留言</label>
<textarea id="message" name="message" rows="6"></textarea>

<select><option>

下拉或可选项列表:

<label for="country">国家</label>
<select id="country" name="country">
  <option value="cn">中国</option>
  <option value="us">美国</option>
</select>

支持 multiple 属性进行多选。可以结合 <optgroup> 分组。

其他:<fieldset><legend><datalist><label>

  • <fieldset> & <legend>:用于分组相关控件并提供组标题,增强语义与无障碍体验。
  • <datalist>:为文本输入提供建议列表(非下拉强制选择)。
<input list="browsers" name="browser">
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
</datalist>

笔者决定先到这么多,后面跟JS联动的时候,我们再进一步的整理相关的内容。

CSS的部分笔记

前言

之前的CSS笔记整理的有些草率,这里重新仔细的整理一部分笔记,重新理解一下CSS的三种文本存在格式:行内(inline)、内联(internal/embedded)、外部(external)。也顺便把选择器梳理一下。

重新看行内(inline)、内联(internal/embedded)、外部(external)

首先,搞清楚定义:

  • 行内样式(inline style):指直接写在 HTML 元素上的 style 属性,例如 <div style="color:red">
  • 内联样式 / 嵌入式样式(internal / embedded):指放在 HTML 文档 <head> 中的 <style>...</style> 块内的样式,作用于该 HTML 文档。
  • 外部样式表(external stylesheet):指放在单独 .css 文件并通过 <link rel="stylesheet" href="..."> 引入的样式。

在代码中,我们直接的体现如下所示

行内样式(inline)

<!-- 直接在元素上写 style -->
<button style="background:#007bff; color:white; padding:8px 12px;">
  提交
</button>

内联/嵌入式样式(internal)

<head>
  <style>
    .btn { background: #007bff; color: white; padding: 8px 12px; }
    @media (max-width: 600px) { .btn { padding: 10px; } }
  </style>
</head>
<body>
  <button class="btn">提交</button>
</body>

外部样式表(external)

<!-- index.html -->
<head>
  <link rel="stylesheet" href="/styles/main.css">
</head>
<body>
  <button class="btn">提交</button>
</body>
/* /styles/main.css */
.btn {
  background: #007bff;
  color: white;
  padding: 8px 12px;
}

优先级的处理问题

CSS的灵活很容易导致一个严肃的问题。大型工程中,如果出现了实际上重复样式渲染的选择,我们如何裁决。这里,CSS的规则如下

  1. !important 声明会提升优先级(先比较 !important,再比较普通声明)。
  2. 同为普通声明时,内联样式(style 属性) 的特异性非常高,会覆盖绝大多数外部/内嵌选择器规则(除非外部/内嵌使用 !important)。
  3. 特异性数值示例(从高到低的“权重”理解):
    • 内联样式(style attribute) → 记作最高(常表述为 1,0,0,0)
    • ID 选择器 #id → 比如 (0,1,0,0)
    • 类/属性/伪类 .class, [attr], :hover → (0,0,1,0)
    • 元素/伪元素 div, p, ::after → (0,0,0,1)
  4. 当特异性相同,后出现的规则覆盖先出现的(CSS 文件加载顺序和位置决定「后来者获胜」)。
<div id="box" class="green" style="color: red;">文本</div>
  • .green { color: green; } 在外部样式中定义,最终文本是 红色(因为内联 style 覆盖)。
  • 如果外部写了 .green { color: green !important; },则 绿色 会胜出(author !important 与 author 普通规则比较时 !important 胜出)。
  • 如果内联也写了 style="color:red !important",那内联的 !important 胜。

三种样式的比较和推介使用

很显然,有三种样式存在的方式,我们就可以在不同的场景下进行灵活选择。

行内样式(inline)

行内样式(inline)的好处就是立刻生效、简洁,适合单次小调整或由 JS 动态设置样式element.style)。对于临时调试、或邮件模板中(很多邮件客户端不支持外部样式)非常有用。但是现代大型工程显然不推介这种方式,因为它破坏结构与样式分离(HTML 混入样式)规则,难维护、重复代码多;特异性很高,容易引发覆盖困难(“特异性战争”)。除了这种维护困难,性能上也会出现大问题:无法被浏览器单独缓存(每个页面都会重复);最致命的也许是与 CSP(Content Security Policy)冲突:许多站点禁止 style-src 'unsafe-inline',所以行内可能被阻止。所以笔者注意到,除非有很正当的理由,否则不要用。

内联/嵌入式样式(<style>

内联/嵌入式样式便于将某个页面的样式集中在同一文件中,适合单页或独立页面的局部样式。但是他也有麻烦,比如说不同页面之间不能被缓存重用(如果多个页面都内嵌同样样式,会重复传输),而且,好像还是没有分离的很开,仍然会给我们的开发造成大量的麻烦,所以这种方式只有在单页应用或页面级样式、调试、快速原型、或构建时将 critical CSS 放到 <head> 中以优化首屏渲染的时候使用

这才是大家使用的最多的方式,外部样式表可以让我们的渲染样式集中、可重用、便于维护、可以被浏览器缓存,减少重复传输;此外易于与构建工具和版本控制配合


CSP(Content Security Policy)与内联样式

很多站点出于安全会设置 CSP,例如:

Content-Security-Policy: style-src 'self';

这会拒绝任何行内样式或未授权的内联 <style>

选择器:选择器的概念与分类(为什么选择器重要)

很经典的问题:你怎么让浏览器知道你要渲染谁,对哪一个元素进行渲染?选择器就是告诉浏览器——你要渲染哪一批元素。他就是CSS 的“定位”机制:告诉浏览器把哪些样式应用到文档中哪些元素上。

  • 简单选择器(类型/类/ID/通配符)
  • 属性选择器(基于元素属性)
  • 组合器(描述元素之间的关系)
  • 伪类 / 伪元素(状态与虚拟元素)
  • 选择器分组(用逗号合并多个选择器)

选择器写得好,样式更可读、可维护、且能避免不必要的渲染开销。


基本(简单)选择器详解

元素选择器(type selector)

这个选择器是直接Target到了指定的元素了,比如说p标签,比如说div标签等等。

p { line-height: 1.6; }
nav { display: flex; }

所以这种标签主要用的是针对全局的设置。

类选择器(class selector)

最常用、推荐的选择方式。以 . 开头,匹配 class 属性包含该类名的元素。

.btn { padding: 8px 12px; }
.card.header { /* 同时包含 .card 和 .header */ }

低特异性、复用性高、和组件化配合良好(BEM 等)。

ID 选择器(id selector)

# 开头,匹配 id 属性相同的单个元素。

#main { max-width: 1200px; margin: 0 auto; }

注意:ID 的特异性高,且通常页面中只应出现一次。现代团队常建议用类替代 ID 做样式(ID 多用于 JS 钩子或锚点)。

通配选择器(universal selector)

*,匹配所有元素(注意性能与广度)。

* { box-sizing: border-box; }

注意的是,这种选择器往往在开发中被禁用掉(或者是拿来做清除某一些效果用的)


属性选择器(attribute selectors)

属性选择器通过元素属性筛选元素,形式灵活,常用于无类库或第三方组件的定制。

  • [attr]:存在某属性
    input[required] { border-left: 3px solid #f00; }
  • [attr="value"]:完全匹配
    a[target="_blank"] { /* 新窗口链接 */ }
  • [attr~="value"]:空格分隔的词列表中匹配一个词(常用于 class 备份场景)
    *[class~="btn"] { /* 等同于 .btn */ }
  • [attr|="value"]:以 value 或 value- 开头(常用于语言)
    html[lang|="en"] { }
  • [attr^="value"]:以 value 开头(prefix)
    a[href^="mailto:"] { /* 邮件链接 */ }
  • [attr$="value"]:以 value 结尾(suffix)
    img[src$=".svg"] { }
  • [attr*="value"]:包含 value(substring)
    a[href*="example.com"] { }

属性选择器对动态属性和第三方生成的 DOM 很有用,但表达式复杂时要注意性能与可读性。


组合器(combinators)与关系选择器

描述元素之间的父子/兄弟/后代关系。

  • 后代选择器(space)A B:匹配位于 A 后代(任意层级)中的 B

    article p { margin-bottom: 1rem; }
    
  • 子选择器(>A > B:匹配 A 的直接元素 B(这里谈到的子是直接的儿子,不包含孙子曾孙子等)

    ul > li { list-style: none; }
    
  • 相邻兄弟选择器(+A + B:匹配直接跟在 A 之后的兄弟 B

    h2 + p { margin-top: 0.25rem; }
    
  • 通用兄弟选择器(~A ~ B:匹配在同一父元素下、位于 A 之后的所有兄弟 B

    input:checked ~ .details { display: block; }
    

选择器越具体、越嵌套越深,维护成本越高。优先使用类并限制层级深度(例如不超过 3 层复杂链)。


伪类(pseudo-classes)与伪元素(pseudo-elements)

常见伪类(表示元素状态)

  • :hover:active:focus:visited:checked:disabled:nth-child():nth-of-type():not() 等。
button:hover { transform: translateY(-2px); }
li:nth-child(odd) { background: #f8f8f8; }
a:visited { color: purple; }

:not() 是选择器否定伪类;注意它本身不增加特异性,但它参数的特异性会被计入

常见伪元素(创建虚拟子元素)

  • ::before::after::first-line::first-letter 等(现代语法使用双冒号 ::,单冒号兼容旧浏览器)。
.btn::before { content: "★ "; }
p::first-letter { font-size: 2em; float: left; }

伪类用于“状态”、伪元素用于“生成内容或样式化文档的一部分”。两类常搭配使用(例如 a:hover::after { ... })。


选择器优先级(specificity)与覆盖规则

基本原则

按优先级顺序比较:

  1. Inline 样式(style="")最高(记作 1,0,0,0
  2. ID 选择器(#id)计入到第二位(比如 0,1,0,0
  3. 类/属性/伪类(.class, [attr], :hover)计入第三位(0,0,1,0
  4. 元素/伪元素(div, ::after)计入第四位(0,0,0,1
  5. !important 可以覆盖常规顺序(优先比较是否 !important,再比较特异性)

表示法:把特异性写成 (a,b,c,d) 四元组,逐位比较,先看 a,再看 b,以此类推。

Example: 数字逐位计算(按要求逐位算清楚)

选择器:#nav .item > a:hover::after

我们把它拆解计数:

  • #nav → ID:计为 b = 1
  • .item → 类:计为 c = 1
  • a → 元素:计为 d = 1
  • :hover → 伪类:也是类级别,计为 c += 1
  • ::after → 伪元素:元素级,计为 d += 1

逐位汇总(按顺序 a,b,c,d):

  • a(inline) = 0
  • b(ID) = 1
  • c(类/伪类/属性) = 1(.item) + 1 (:hover) = 2
  • d(元素/伪元素) = 1 (a) + 1 (::after) = 2

最终特异性 = (0, 1, 2, 2)。任何具有更高 b(ID)或在 b 相同下更高 c 的规则会覆盖本规则;若所有位相同,则文档中后出现的规则胜出(later wins)。


7 — 选择器分组、层叠规则与顺序

  • 分组选择器(用逗号):复用样式,等价于多条独立规则,但写起来更短。
h1, h2, h3 { font-family: "Noto Sans", sans-serif; }
  • 层叠(Cascading)规则:当多个规则匹配同一元素,按照先比较 !important,再比较来源(user/author/UA),再比较特异性,最后比较出现顺序(later wins)。

提示:常见错误是把样式写在多个文件,然后忘了加载顺序,导致覆盖混乱。保持样式文件结构清晰,约定好全局与组件样式的加载顺序。