已经完成的部分
- Day02 如何写好JS - 月影
- Day04-2 前端动画实现 - 蒋翔
- Day08-1 TypeScript入门 - 林皇
- Day09-2 前端设计模式应用 - 吴立宁
- Day04-1 响应式系统与React - 牛岳
- Day06-2 Web开发的安全之旅 - 刘宇晨
- Day01-1 前端与HTML - 韩广军
- Day01-2 理解CSS - 韩广军
- Day01-3 深入CSS - 韩广军
- Day10-1 Web多媒体入门 - 刘立国
- Day07 构建webpack知识体系 - 范文杰
- Day05-1 HTTP实用指南 - 杨超男
- Day03-2 前端调试知识 - 秃头披风侠
- Day06-1 初识WebGL - 月影
- Day05-2 Node.js与前端开发实战 - 欧阳亚东
- Day09-1 小程序技术全解 - 张晓伟
- Day08-2 小游戏开发 - ycaptain
- Day03-1 Web标准与前端开发 - 李松峰
- Day10-2 数据可视化基础 - 何菲菲
Day0 字节笔试
-
不定项选择:JS基础, 事件冒泡等内容
-
编程题
判断输入的字符串是不是4的整数幂
标准解法当然是位运算, 我比较懒, 读取数字, 求
log(t)/log(4)是不是整数, 其实应该用algorithm库中更快速的的log_2(), 无奈在牛客的代码提示中没找到这个函数输入一个字符串, 输出全排列
标准解法当时是搜索/康托, 我比较懒, 直接调
std::next_permutation(begin, end)
Day1-1 前端与HTML - 韩广军
前端职责:图形界面下的人机交互问题
前端技术栈
- HTML:结构
- CSS:样式
- JS:交互
- http协议:传输
前端需要关注问题
- 美观
- 功能
- 无障碍(不只是对于残障人士, 还包括一些困难的场景, 例如: 在抖动的环境下按一个小按钮)
- 安全
- 兼容性
- 用户体验
前端可以实现的功能
- 服务端(Node.JS)
- 用户端(Election)
- 3D(webGL)
- 语音交互(webRTC)
- 代码执行(WebAssembly)
HTML是什么
- HyperText: 图片, 标签, 音视频
- Markup Language: 标签, 属性键值
HTML语法
- 不区分大小写(推荐小写)
- 空标签可以不闭合或者加
/闭合(<input>,<img />) - 属性值用双引号闭合
- 部分标签属性值可以省略(required, readonly…)
浏览器拿到HTML后会将嵌套关系转化为一个DOM树
HTML标签复习
-
<!doctype html>: 指明HTML版本(不写的话浏览器按照老版本标准(兼容模式)执行) -
<html>根标签 -
<head>存放元数据 -
<body>需要呈现的内容 -
标题:
<h1/>-<h6/>默认样式从大到小 -
列表:
-
有序列表
<ol> <li>1</li> <li>2</li> <li>3</li> </ol> -
无序列表
<ul> <li>1</li> <li>2</li> <li>3</li> </ul> -
定义列表(注意,支持多对多)
<dl> <dt>导演</dt> <dd>陈凯歌</dd> <dt>主演</dt> <dd>张国荣</dd> <dd>张丰毅</dd> <dt>出品</dt> <dt>宣发</dt> <dd>巩俐</dd> </dl>-
导演
- 陈凯歌 主演
- 张国荣
- 张丰毅 出品 宣发
- 巩俐
-
-
链接:
<a href="" target="" />最重要的两个属性是href与target -
多媒体:
<img/>,<audio></auduio>,<video></video>- alt: 不被加载的时候的替换内容(不被加载的情况包括: 加载失败, 用户开启省流模式)
- 音视频自动播放属性:
- autoplay: 如果用户之前访问过这个域名, 还手动播放该域名下的视频过才会自动播放
- controls: 规定浏览器应该为视频提供播放控件
- muted: 静音播放
- 音视频自动播放属性:
- 视频静音(通过加上 muted 属性)或音量设置为 0
- 用户与网站进行了交互(通过点击, 敲击, 按键等)
- 站点已被列入浏览器白名单;
- 浏览器确定用户频繁使用媒体, 自动加入白名单或者通过首选项或其他用户界面功能手动发生
- 自动播放功能策略授予了
<iframe>及其文档自动播放支持. - 用户已将该站点添加到其移动设备的主屏幕或在桌面设备上安装了 PWA.
-
输入:
-
<input placeholder="" type=""/>-
type="range" -
type= "number" -
type= "date" -
type= "checkbox"通过name进行分组互斥, 通过<label for="">打标记 -
type= "radio"通过name进行分组互斥, 通过<label for="">打标记 -
Apple Orange Pineapple Bananatype= "select"下拉框<select> <option>Apple</option> <option>Orange</option> <option>Pineapple</option> <option>Banana</option> </select>
-
-
可选下拉列表输入框(可以选择,也可以随便输入内容,如果输入的是选项的前缀会自动跳出提示)
<input list="ice-cream-flavors" id="ice-cream-choice" name="ice-cream-choice" /> <datalist id="ice-cream-flavors"> <option value="Chocolate"> <option value="Coconut"> <option value="Mint"> <option value="Strawberry"> <option value="Vanilla"> </datalist> -
多行输入框:
<textarea></textarea>
-
-
文本标签
- 引用
- 块级别引用:
<blockquote cite=""></blockquote> - 短引用:
<cite></cite>(一般写一些作品名) - 引用之前页面中的内容
<q></q>
- 块级别引用:
- 代码
- 单行代码
<code></code> - 多行代码
<pre><code></code></pre>
- 单行代码
- 强调
<strong></strong>表示语意上重要 紧急<em></em>语气上的重读
- 引用
-
版块划分
- 页头
- header
- nav
- 主体
- main
- 与内容相关但不属于主体内容的(信息推荐, 广告): aside
- 页尾:
<footer></footer>
- 页头
语义化的作用:
- 开发者:方便修改与维护
- 浏览器:展示页面
- 搜索引擎:提取关键词, 排序
- 屏幕阅读器
跨域问题
<script>, <a>是完全支持跨域的, 在Chrome97中https页面不能跨域引用来自http页面的图片等资源
自定义标签
webComponment是一套不同的技术, 允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们.
- 目的: 实现复杂HTML组件的复用
- 技术组成:
- Custom elements(自定义元素)
- Shadow DOM(影子DOM)
- HTML templates(HTML模板)
shadow DOM允许在文档流中创建一些完全独立于其他DOM的子DOM树(不会使用页面的JS/CSS, 在DevTools中现实的也是#shadowDOM字样). 于是我们可以封装一个具有独立功能的组件, 并完全不会干扰其他DOM与被其他DOM干扰. 在shadow DOM内部我们可以定义元素的样式与行为
有这样的术语
- Shadow host: Shadow DOM挂载到的DOM节点.
- Shadow tree: Shadow DOM内部的DOM树.
- Shadow boundary: Shadow DOM结束的地方, 也是常规 DOM开始的地方.
- Shadow root: Shadow tree的根节点.
方法
Element.attachShadow(): 将一个 shadow root 附加到任何一个元素上. 它接受一个配置对象{mode: 'open'|'closed'}分别表示这个DOM是否可以被页面的JS抓取
类似于响应式框架中的模板, 开发者通过模板来复用一些 HTML 代码段, 在 HTML5 标准下我们甚至不需要 javascript 框架就能轻松使用模板.
<template id="my-paragraph">
<p>My paragraph</p>
</template>
在模板中创建 HTML 代码块和子 DOM 树, 使得我们可以用不同的物理文件来组织代码. 通过<link>标签来引入这些文件, link到的数据并不会展示到DOM, 需要手动添加
<head>
<link rel="import" href="components/some.html" />
</head>
<body>
<!--some DOM-->
<my-paragraph id="my-paragraph"></my-paragraph>
<!--some DOM-->
</body>
还可以通过slots增加灵活性: 也有默认插槽, 具名插槽等. 见MDN
我们声明一个语义化的自定义元素来引用组件, 用 javascript 建立自定义元素和模板, shadow DOM 之间的关联, 然后将自定义标签插入到页面上就能得到一个封装好的组件.
"自定义元素的名字必须包含一个破折号(
-)所以<x-tags>,<my-element>和<my-awesome-app>都是正确的名字, 而<tabs>和<foo_bar>是不正确的. 这样的限制使得 HTML 解析器可以分辨那些是标准元素, 哪些是自定义元素. "
浏览器提供了两种类型对象HTMLUnknownElement和HTMLElement
- HTML标准元素在浏览器中会被解析为
HTMLElement实例 - 用户没有声明的自定义元素会被解析为
HTMLUnknownElement - 一旦自定义元素中使用了破折号此时自定义元素就变为了
HTMLElement
使用
// 定义一个 <my-element></my-element>
class MyElement extends HTMLElement {
...}
window.customElements.define('my-element', MyElement);
上面代码中, 原生的window.customElements对象的define方法用来定义 Custom Element. 该方法接受两个参数, 第一个参数是自定义元素的名字, 第二个参数是一个 ES6 的class.
这个class使用get和set方法定义 Custom Element 的某个属性.
class MyElement extends HTMLElement {
get content() {
return this.getAttribute('content');
}
set content(val) {
this.setAttribute('content', val);
}
}
有了这个定义, 网页之中就可以插入<my-element>了.
<my-element content="Custom Element">
Hello
</my-element>
使用div代替button
可以这么代替, 但是不仅要复刻样式, 还要加入属性role="button"方便浏览器赋予聚焦等属性与无障碍优化(Aria,Roles)
HTML5离线工作
- 前端缓存方案:
localStorage,sessionStorage - 使用Service Worker进行离线后行为管理与通信
Service workers 本质上充当 Web 应用程序, 浏览器与网络(可用时)之间的代理服务器. 这个 API 旨在创建有效的离线体验, 它会拦截网络请求并根据网络是否可用来采取适当的动作, 更新来自服务器的的资源. 它还提供入口以推送通知和访问后台同步 API. Service workers 是基于 HTTPS 的, 因为Service Worker中涉及到请求拦截, 所以必须使用HTTPS协议来保障安全.
Service workers生命周期
-
注册: 要使用Service Worker, 首先需要注册一个sw, 通知浏览器为该页面分配一块内存, 然后sw就会进入安装阶段.
(function() { if('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js'); } })() // 只针对部分页面进行注册 navigator.serviceWorker.register('/topics/sw.js'); // 另一种作用域限定 navigator.serviceWorker.register('sw.js', { scope: './' }); -
安装: sw开始缓存文件了, 检查所有文件的缓存状态, 如果都已经缓存了, 则安装成功, 进入下一阶段.
-
激活: 如果是第一次加载sw, 在安装后, 会直接进入activated阶段, 而如果sw进行更新, 情况就会显得复杂一些(新版本安装, 老版本工作 - 新版本开始waiting, 等待老版本terminated - 新版本替换)
-
空闲: 空闲状态一般是不可见的, 这种一般说明sw的事情都处理完毕了,然后处于闲置状态了,浏览器会周期性的轮询, 去释放处于idle的sw占用的资源
-
终止: 终止状态一般触发条件由下面几种方式
- 关闭浏览器一段时间
- 手动清除serviceworker
- 在sw安装时直接跳过waiting阶段
self.addEventListener('install', function(event) { //跳过等待过程 self.skipWaiting(); }); -
拦截: sw最终要和关键阶段,主要用于拦截代理所有指定的请求,然后进行二次相应的处理操作
Day1-2 理解CSS - 韩广军
CSS: Cascading Style Sheets 层叠样式表, 用来定义页面元素的样式
使用CSS的三种方法: 外链,嵌入,内联(组件式开发看起来是新的模式)
CSS的加载过程
选择器复习
- 通配选择器
* - 标签选择器
tagName - id选择器
#id - 类选择器
.class - 属性选择器
- 没有值只有键
[disabled] - 完全匹配值
[type="pwd"] - 开头
[type^="pwd"] - 结尾
[type$="pwd"]
- 没有值只有键
- 伪类选择器
- 状态伪类:a有LVHA,在按下后有:force状态(LVFHA)
- 结构伪类:first-child…
- 选择器组合
AB同时满足ABA B选择B如果B是A的子孙A>B选择B如果B是A的子元素A~B选择B如果B在A后且与A同级A+B选择B如果他紧随在A后面
颜色表示复习
- RGB: 三原色混合, RGB值的变化不能直观表示颜色的变化, 不方面操作颜色
- HSL: 更加直观方便操作
- Hue: 色相, 表示颜色基本属性, 值为0-360表示色环上的色值
- Saturation: 饱和度, 表示颜色的鲜艳程度, 值为0-100%, 越高越鲜艳, 越低越灰
- Lightness: 亮度, 值为0-100%, 越高越白越低越灰(例如想要让btn变灰只需要调低L)
- alpha透明度: 可以与rgb/hsl组成rgba/hsla, 值为0-1/映射到0-255后转Hex
字体使用复习
font-family: 指定使用的字体组合, 由于浏览器使用字体基于用户安装, 所以要多指定几组, 并且在最后使用字体类型兜底, 例如font-family:'Segoe UI', Tahoma, sans-serif
字体类型
- serif: 衬线字体, 在字末尾有一定装饰(Georgia, 宋体)
- sans-serif: 无衬线字体, 线条单一, 末尾没有装饰(Arial, Helvetica, 黑体)
- cursive: 手写体(Caflisch Script, 楷体)
- fantasy: 艺术体
- monosapce: 等宽字体(Consolas, Courier, 中文字体)
在使用font-family的时候注意
- 一般在font-family最后指定一个字体类型兜底
- 先英文后中文, 原因是浏览器在渲染字符的时候会逐字符比对使用的字体, 一般中文字体中就包含了英文字体, 如果中文在前, 为英文字体准备的字体就不会匹配了
使用自定义字体
<style>
@font-face {
font-family: f1;
src: url("//s2.ssl.qhimg.com/static/ff00cb8151eeecd2.woff2") format("woff2");
}
</style>
<h1 style="font-family: f1, serif">落霞与孤鹜齐飞, 秋水共长天一色. </h1>
对于英文字符这这种方法可以快速引入字体, 但是对于中文字符来说, 中文字体包太大了, 一般需要裁剪字体包获得需要的字符
字体样式
font-size: 设置字体大小- 关键字: small medium large
- 长度单位: px em
- 百分数: 相对于父元素字体大小
font-style: 设置字体风格normal: 正常italic: 斜体oblique: 倾斜
font-weight: 设置字重- 关键字: normal, bold…
- 数值: 100-900(400=normal,700=bold), 如果系统中找不到字体则会就近替换
line-height: 设置行高(baseline之间的距离)font: 字体设置简写([斜体] 粗细 大小[/行高] [字体族])
段落属性
text-align: 对齐方式 left|center|right|justify(分散对齐, 且仅对非最后一行有效, 如果文字只有一行那也不生效)letter-spacing: Xpx字符之间的距离word-spacing: Xpx字符之间的距离text-indent: Xpx首行缩进(支持负数)text-decoration: underline|line-through|overline|none下划线, 删除线, 上划线, 无white-space: normal(默认)|nowrap(不换行)|pre(保留所有换行与空格)|pre-wrap(保留空格, 会换行)|pre-wrap(合并空格, 保留换行)
原子化CSS(Windi CSS)
原子化 CSS 是一种 CSS 的架构方式, 它倾向于小巧且用途单一的 class, 并且会以视觉效果进行命名
CSS原子化是一种写法, 其特点就是一个类名对应一个样式, 从而通过在标签上附加不同的类名来生成对应的效果, 可以有效的减少CSS的相关代码, 例如
.flex {
display: flex;
}
坏处
- 需要定制一大堆工具类, 这些使用的话需要开发人员的熟悉.
- 虽然减少了CSS的代码, 但是使得HTML的代码变得更臃肿了.
- CSS 规则插入顺序仍然很重要.
好处
- 写法真的变简单了, 比如添加一个常用的规则时, 直接就加原子类
- 原子类是全局的, 所以当我们移动一段标签到其他页面时, 样式也就是有的
- 当我们使用原子类的时候, 我们修改页面的样式时往往不会再去修改
CSS代码了, 而是直接修改原子类名
框架: tailwindcss
Tailwind CSS 是一个功能类优先的 CSS 框架, 它集成了诸如
flex,pt-4,text-center和rotate-90这样的的类, 它们能直接在脚本标记语言中组合起来, 构建出任何设计.
是不是就是CSS原子类的写法了?当然, tailwindcss不止于此, 它还有很多强大的功能和配置
优雅降级与渐进增强
CSS构建的两种观点
- "优雅降级"观点
“优雅降级"观点认为应该针对那些最高级, 最完善的浏览器来设计网站. 而将那些被认为"过时"或有功能缺失的浏览器下的测试工作安排在开发周期的最后阶段, 并把测试对象限定为主流浏览器(如 IE, Mozilla 等)的前一个版本. 在这种设计范例下, 旧版的浏览器被认为仅能提供"简陋却无妨 (poor, but passable)” 的浏览体验. 你可以做一些小的调整来适应某个特定的浏览器. 但由于它们并非我们所关注的焦点, 因此除了修复较大的错误之外, 其它的差异将被直接忽略. - "渐进增强"观点
"渐进增强"观点则认为应关注于内容本身. 内容是我们建立网站的诱因. 有的网站展示它, 有的则收集它, 有的寻求, 有的操作, 还有的网站甚至会包含以上的种种, 但相同点是它们全都涉及到内容. 这使得"渐进增强"成为一种更为合理的设计范例. 这也是它立即被 Yahoo! 所采纳并用以构建其"分级式浏览器支持 (Graded Browser Support)"策略的原因所在.
.transition {
/*渐进增强写法*/
-webkit-transition: all .5s;
-moz-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
.transition {
/*优雅降级写法*/
transition: all .5s;
-o-transition: all .5s;
-moz-transition: all .5s;
-webkit-transition: all .5s;
}
CSSinJS
CSS默认是全局生效的, 可以使用CSS in JS, scoped等方案实现, CSS in JS是一种CSS工程化方案
传统CSS问题
- 全局污染 - CSS的选择器是全局生效的, 所以在class名称比较简单时, 容易引起全局选择器冲突, 导致样式互相影响.
- 命名混乱 - 因为怕全局污染, 所以日常起class名称时会尽量加长, 这样不容易重复, 但当项目由多人维护时, 很容易导致命名风格不统一.
- 样式重用困难 - 有时虽然知道项目上已有一些相似的样式, 但因为怕互相影响, 不敢重用.
- 代码冗余 - 由于样式重用的困难性等问题, 导致代码冗余.
为解决问题而提出的规范
- SASS, LESS - 提供了变量, 简单函数, 运算, 继承等, 扩展性, 重用性都有了很大的提升, 解决了一些样式重用冗余的问题, 但是对于命名混乱问题的效果不大.
- BEM (.block__element–modifier) - 比较流行的class命名规则, 部分解决了命名混乱和全局污染的问题, 但class定义起来还是不太方便, 比较冗长, 而且和第三方库的命名还是有可能冲突.
- CSS Modules - 模块化CSS, 将CSS文件
import到JavaScript里并声明为字符串, 在使用的时候拼串导出. 基本上解决了全局污染, 命名混乱, 样式重用和冗余的问题, 但CSS有嵌套结构的限制(只能一层), 也无法方便的在CSS和JavaScript之间共享变量.
CSS in JS就是将应用的CSS样式写在JavaScript文件里面, 可以利用标签的style拼串, 也可以利用ele.style实现
一些不知道的伪元素
::first-letter: 首字母::first-line: 首行::cue (:cue): 用于选中VTT视频中的怪东西::selection: 选中被用户高亮的/选中的内容::slotted(): 选中HTML模板元素
见MDN
Day1-3 深入CSS - 韩广军
CSS选择器的特异程度: ID选择器数目_(伪)类选择器数目_标签选择器数目拼串
继承
部分属性可以自动继承其来自父元素的计算值, 除非显式指定
- 可以继承的样式: 与文字相关的
- 不可以继承的属性: 宽度, 高度, 盒子模型, 尺寸相关, **对于不可继承的属性可以通过
key:inherit**继承 - 如果元素想要继承的值父元素们都没有, 那么其会自动使用默认值. 可以使用
key:initial显式使用默认值 - 还可以使用
key:unset, 若该CSS属性可继承, 则从父级继承对应属性值, 若该CSS性不可继承, 则将其重置为初始值
CSS的求值过程
- filtering: 找到可以匹配到元素的规则
- cascading: 根据选择器特异性找到优先级最高的属性值
- defauting: 对于没有匹配到的属性值, 要么继承父类, 要么使用默认值
- resolving: 将相对值(red, rm)转化为浏览器下的值(#f00, px), 这一部分不转换需要部署才知道的值(
em可以转为px,width:60%需要布局后才知道是多少, 在这一步不转化), 此处得到的结果叫计算值, 子元素样式为inherit的时候就是参考了父元素的计算值 - formatting: 将相对值进一步转化(例如
width:60%) - constraining: 将小数像素转化为整数像素, 并根据特殊规则进行调整(
min-width, chrome中的font-size:10px会转化为12px)最终应用在页面上
布局
布局相关技术
- 常规流(文档流)
- 行级
- 块级
- 表格布局
- FlexBox
- Grid布局
- 浮动
- 绝对定位
盒子模型
当padding设置为百分比的时候padding参考的是父元素的宽度
据此实现一个1:1的盒子
<style>
.wap{
width: 200px;
height: 300px;
background-color: #bfa;
}
.cont{
width: 100%;
height: 0;
padding-bottom: 100%;
background-color: red;
}
</style>
<body>
<div class="wap">
<div class="cont"></div>
</div>
</body>
当margin设置为百分比的时候padding参考的是父元素的宽度
垂直方向margin会合并折叠
块级元素与行内元素
- 块级元素不能并排摆放, 适用所有盒模型
- 行内元素可以与其他行内元素拜访, 但是盒模型width, height不适用
- inline-block本身是行内元素, 但是被放在了盒子中, 适用盒模型width, height
Flex Box
-
flex-dirextion: row|row-reverse|column|column-reverse: 摆放流向 -
justify-content: flex-start|flex-end|center|space-between|space-around|space-evenly: 主轴对齐方式 -
align-items: flex-start|flex-end|center|stretch|baseline侧轴对齐 -
align-self: 为特定元素设置侧轴对齐方式 -
order: 手动指定顺序 -
flex-grow: 当容器有剩余空间的时候的伸展能力 -
flex-shrink: 当容器剩余空间不足的时候的压缩能力(默认是1) -
flex-base: 容器的自然长度 -
flex: 缩写- 单值语法: 值必须为以下其中之一:
- 一个无单位数(
<number>): 它会被当作flex:<number>1 0;<flex-shrink>的值被假定为1, 然后<flex-basis>的值被假定为0. - 一个有效的宽度(width)值: 它会被当作
<flex-basis>的值. - 关键字none, auto或initial.
- 一个无单位数(
- 双值语法: 第一个值必须为一个无单位数, 并且它会被当作
<flex-grow>的值. 第二个值必须为以下之一:- 一个无单位数:它会被当作
<flex-shrink>的值. - 一个有效的宽度值: 它会被当作
<flex-basis>的值.
- 一个无单位数:它会被当作
- 三值语法:
- 第一个值必须为一个无单位数, 并且它会被当作
<flex-grow>的值. - 第二个值必须为一个无单位数, 并且它会被当作
<flex-shrink>的值. - 第三个值必须为一个有效的宽度值, 并且它会被当作
<flex-basis>的值.
- 第一个值必须为一个无单位数, 并且它会被当作
/* 一个值, 无单位数字: flex-grow */ flex: 2; /* 一个值, width/height: flex-basis */ flex: 10em; flex: 30px; flex: min-content; /* 两个值: flex-grow | flex-basis */ flex: 1 30px; /* 两个值: flex-grow | flex-shrink */ flex: 2 2; /* 三个值: flex-grow | flex-shrink | flex-basis */ flex: 2 2 10%; /*全局属性值 */ flex: inherit; flex: initial; flex: unset; - 单值语法: 值必须为以下其中之一:
Grid 布局
- 使用
display: grid创建网格容器 - 使用
grid-template-X: 将容器划分为网格 - 设置每一个子元素占用那些行列
- 使用
grid-area: X/X/X/X或grid-column/row-start/end指定元素所占位置, 允许重叠
Float 布局
本质是做图文环绕的, 在没有先进布局的时候还用来做各种布局, 现在已经不需要float做布局了, 除了图文环绕, 其他不需要用float做
Position
static: 默认relative: 相对元素本身定位absolute: 相对非static祖先定位fixed: 相对于视口定位
Day2 如何写好JS - 月影
原则-各司其责
我们希望让HTML, CSS, JS分别去控制结构, 表现, 功能. 各司其职并不是让他们物理上分类(写成三个文件), 而是让他们功能上分离(要不然现在组件化开发岂不是完全违反了原则), 例如不要让JS去直接操作CSS样式
举例: 请实现一个静态页面的深夜模式
-
最基础的版本
<header> <button id="modeBtn">🌞</button> <h1>深夜食堂</h1> </header>const btn = document.getElementById('modeBtn'); btn.addEventListener('click', (e) => { const body = document.body; if(e.target.innerHTML === '🌞') { body.style.backgroundColor = 'black'; body.style.color = 'white'; e.target.innerHTML = '🌜'; } else { body.style.backgroundColor = 'white'; body.style.color = 'black'; e.target.innerHTML = '🌞'; } });有问题: 通过文字判断状态, 使用JS直接操作了一个CSS样式
-
改一改
body.night { background-color: black; color: white; transition: all 1s; } #modeBtn::after { content: '🌞'; } body.night #modeBtn::after { content: '🌜'; }const btn = document.getElementById('modeBtn'); btn.addEventListener('click', (e) => { const body = document.body; if(body.className !== 'night') { body.className = 'night'; } else { body.className = ''; } });定义了该
night样式, 不用通过文字判断状态了. 要是让我写我最多改成classList.toggle() -
回头想想: 我们在做什么事情? 我们在做一个纯视觉效果展示, 样式应该是CSS控制的! JS是控制行为的, 我们可以尝试纯CSS+HTML实现
<input id="modeCheckBox" type="checkbox"> <div class="content"> <header> <label id="modeBtn" for="modeCheckBox"></label> <h1>深夜食堂</h1> </header> </div>#modeCheckBox { display: none; } #modeCheckBox:checked + .content { background-color: black; color: white; transition: all 1s; } #modeBtn { font-size: 2rem; float: right; } #modeBtn::after { content: '🌞'; } #modeCheckBox:checked + .content #modeBtn::after { content: '🌜'; }直接使用
checkbox用于点击, 通过选择checkbox的checked伪类来定义夜间模式要是之和我这么一说, 我估计要这么写
- <input id="modeCheckBox" type="checkbox"> <div class="content"> <header> + <input id="modeCheckBox" type="checkbox"> <label id="modeBtn" for="modeCheckBox"></label> <h1>深夜食堂</h1> </header> </div>#modeCheckBox { - display: none; + outline: none; + width: 0; + height: 0; } // ...很自然的, 我把
input与label放一起了, 然后把在关掉checkbox的样式, 但是这不符合语义化要求啊!!!, 回头想想, 为啥要专门有个label标签呢? 为的是让他们永远在一起吗? 当然是可以让这两个标签分开啊人家还可以把这个
checkbox给删掉, 太他妈牛了. 这才是语义化, 样式与表现各司其职
我们学到了
- 什么是HTML, CSS, JS各司其职
- 不要让JS直接干预Style
- 用class表示状态
- 对于样式的展示尽量寻求零JS方案
原则-组件封装
我们希望从Web页面中抽象出一个一个包含HTML, CSS, JS的组件出来, 使得组件具备良好的封装性, 正确性, 可扩展性, 复用性. 我们将通过封装一个轮播图组件来了解组件封装的过程
-
结构设计:
轮播图显然是一个列表结构, 使用
ul即可 -
样式设计:
使用CSS绝对定位将图片重叠在一起, 使用修饰符定义active元素, 使用transition切换动画
-
行为设计-API设计(功能设计):
应该保证API是: 原子操作, 职责单一, 满足灵活性
我们设计一个
Slide类class Slider{ constructor(id){ this.container = document.getElementById(id); this.items = this.container .querySelectorAll('.slider-list__item, .slider-list__item--selected'); } getSelectedItem(){ // 活动元素 const selected = this.container .querySelector('.slider-list__item--selected'); return selected } getSelectedItemIndex(){ // 活动元素index return Array.from(this.items).indexOf(this.getSelectedItem()); } slideTo(idx){ // 滚到 const selected = this.getSelectedItem(); if(selected){ selected.className = 'slider-list__item'; } const item = this.items[idx]; if(item){ item.className = 'slider-list__item--selected'; } } slideNext(){ // 下一个 const currentIdx = this.getSelectedItemIndex(); const nextIdx = (currentIdx + 1) % this.items.length; this.slideTo(nextIdx); } slidePrevious(){ // 上一个 const currentIdx = this.getSelectedItemIndex(); const previousIdx = (this.items.length + currentIdx - 1) % this.items.length; this.slideTo(previousIdx); } } const slider = new Slider('my-slider'); slider.slideTo(3); -
行为设计-控制流
组件应该是在DOM上可控制的(总不能让用户输入
slider.slideNext切换吧), 我们需要完成DOM状态与API行为的耦合我们可以通过自定义事件进行解耦(尽量让DOM状态的改变与我们的代码之间独立)
为
Slide类加入start与stop实现控制, 在构造函数中加入控制器的时间绑定class Slider{ constructor(id, cycle = 3000){ this.container = document.getElementById(id); this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected'); this.cycle = cycle; const controller = this.container.querySelector('.slide-list__control'); if(controller){ const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected'); controller.addEventListener('mouseover', evt=>{ const idx = Array.from(buttons).indexOf(evt.target); if(idx >= 0){ this.slideTo(idx); this.stop(); } }); controller.addEventListener('mouseout', evt=>{ this.start(); }); this.container.addEventListener('slide', evt => { const idx = evt.detail.index const selected = controller.querySelector('.slide-list__control-buttons--selected'); if(selected) selected.className = 'slide-list__control-buttons'; buttons[idx].className = 'slide-list__control-buttons--selected'; }) } const previous = this.container.querySelector('.slide-list__previous'); if(previous){ previous.addEventListener('click', evt => { this.stop(); this.slidePrevious(); this.start(); evt.preventDefault(); }); } const next = this.container.querySelector('.slide-list__next'); if(next){ next.addEventListener('click', evt => { this.stop(); this.slideNext(); this.start(); evt.preventDefault(); }); } } getSelectedItem(){ /*...*/} getSelectedItemIndex(){ /*...*/} slideNext(){
最低0.47元/天 解锁文章
789

被折叠的 条评论
为什么被折叠?



