前端架构是为了在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
微前端框架内的各个应用都支持独立开发部署、不限技术框架、支持独立运行、应用状态隔离但也可共享等特征。
本文会从框架的应用隔离实现方案、实战、优缺点三个方面探一探各个框架。帮助大家了解各个框架是如何使用,如何运行,从而能选出适合自己项目的微前端方案。
在没有各大微前端解决方案之前,iframe是解决这类问题的不二之选,因为iframe提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。
但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题:
目前(2024年4月)github star 13k
Single-spa(https://github.com/single-spa/single-spa) 是最早的微前端框架,兼容多种前端技术栈;是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架;
简单来说就是一个聚合,使用这个库可以让你的应用可以 使用多个不同的技术栈(vue、react、angular等等)进行同步开发,最后使用一个公用的路由去实现完美的切换;
Single-spa 实现了一套生命周期,开发者需要在相应的时机自己去加载对应的子应用。
它做的事情就是注册子应用、监听 URL 变化,然后加载对应的子应用js,执行对应子应用的生命周期流程。
主要通过single-spa提供的registerApplication方法注册子应用,子应用需要指定加载子应用的方法、和路由条件。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerApplication, start } from 'single-spa'
Vue.config.productionTip=false
// 远程加载子应用
function createScript(url) {
return new Promise((resolve, reject)=> {
const script=document.createElement('script')
script.src=url
script.onload=resolve
script.onerror=reject
const firstScript=document.getElementsByTagName('script')[0]
firstScript.parentNode.insertBefore(script, firstScript)
})
}
// 记载函数,返回一个 promise
function loadApp(url, globalVar) {
// 支持远程加载子应用
return async ()=> {
await createScript(url + '/js/chunk-vendors.js')
await createScript(url + '/js/app.js')
// 这里的return很重要,需要从这个全局对象中拿到子应用暴露出来的生命周期函数
return window[globalVar]
}
}
// 子应用列表
const apps=[
{
// 子应用名称
name: 'app1',
// 子应用加载函数,是一个promise
app: loadApp('http://localhost:8081', 'app1'),
// 当路由满足条件时(返回true),激活(挂载)子应用
activeWhen: location=> location.pathname.startsWith('/app1'),
// 传递给子应用的对象
customProps: {}
},
{
name: 'app2',
app: loadApp('http://localhost:8082', 'app2'),
activeWhen: location=> location.pathname.startsWith('/app2'),
customProps: {}
},
{
// 子应用名称
name: 'app3',
// 子应用加载函数,是一个promise
app: loadApp('http://localhost:3000', 'app3'),
// 当路由满足条件时(返回true),激活(挂载)子应用
activeWhen: location=> location.pathname.startsWith('/app3'),
// 传递给子应用的对象,这个很重要,该配置告诉react子应用自己的容器元素是什么,这块儿和vue子应用的集成不一样,官网并没有说这部分,或者我没找到,是通过看single-spa-react源码知道的
customProps: {
domElement: document.getElementById('microApp'),
// 添加 name 属性是为了兼容自己写的lyn-single-spa,原生的不需要,当然加了也不影响
name: 'app3'
}
}
]
// 注册子应用
for (let i=apps.length - 1; i >=0; i--) {
registerApplication(apps[i])
}
new Vue({
router,
mounted() {
// 启动
start()
},
render: h=> h(App)
}).$mount('#app')
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'
import { BrowserRouter, Link, Route } from 'react-router-dom'
import singleSpaReact from 'single-spa-react'
// 子应用独立运行
if (!window.singleSpaNavigate) {
ReactDOM.render(rootComponent(), document.getElementById('root'))
}
// 生命周期a
const reactLifecycles=singleSpaReact({
React,
ReactDOM,
rootComponent,
errorBoundary(err, info, props) {
return <div>
This renders when a catastrophic error occurs
</div>
}
})
// 这里和vue不一样,props必须向下传递
export const bootstrap=async props=> {
console.log('app3 bootstrap');
return reactLifecycles.bootstrap(props)
}
export const mount=async props=> {
console.log('app3 mount');
return reactLifecycles.mount(props);
}
export const unmount=async props=> {
console.log('app3 unmount');
return reactLifecycles.unmount(props)
}
// 根组件
function rootComponent() {
return <React.StrictMode>
<BrowserRouter>
<div>
<Link to="/app3">Home</Link> |
<Link to="/app3/about"> About</Link>
<Route exact path="/app3" component={Home} />
<Route exact path="/app3/about" component={About} />
</div>
</BrowserRouter>
</React.StrictMode>
}
// home 组件
function Home() {
return <div>
<h1>app3 home page</h1>
</div>
}
// about 组件
function About() {
return <div>
<h1>app3 about page</h1>
</div>
}
将子应用导出模式设置为umd
const package=require('./package.json')
module.exports={
// 告诉子应用在这个地址加载静态资源,否则会去基座应用的域名下加载
publicPath: '//localhost:8082',
// 开发服务器
devServer: {
port: 8082
},
configureWebpack: {
// 导出umd格式的包,在全局对象上挂载属性package.name,基座应用需要通过这个全局对象获取一些信息,比如子应用导出的生命周期函数
output: {
// library的值在所有子应用中需要唯一
library: package.name,
libraryTarget: 'umd'
}
}
}
可以看到它是动态加载的子应用的js,并执行js,将内容渲染到了主应用的盒子内。
目前(2024年4月) github star 15.4k
阿里的qiankun 是一个基于 single-spa 的微前端实现库,孵化自蚂蚁金融,帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
// 伪代码
class ProxySandbox {
constructor() {
const rawWindow=window;
const fakeWindow={}
const proxy=new Proxy(fakeWindow, {
set(target, p, value) {
target[p]=value;
return true
},
get(target, p) {
return target[p] || rawWindow[p];
}
});
this.proxy=proxy
}
}
let sandbox1=new ProxySandbox();
let sandbox2=new ProxySandbox();
window.a=1;
// 伪代码
((window)=> {
window.a='hello';
console.log(window.a) // hello
})(sandbox1.proxy);
((window)=> {
window.a='world';
console.log(window.a) // world
})(sandbox2.proxy);
// 伪代码
class SnapshotSandbox {
constructor() {
this.proxy=window;
this.modifyPropsMap={}; // 修改了那些属性
this.active(); // 调用active保存主应用window快照
}
/**1. 初始化时,在子应用即将mount前,先调用active,保存当前主应用的window快照*/
active() {
this.windowSnapshot={}; // window对象的快照
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
// 将window上的属性进行拍照
this.windowSnapshot[prop]=window[prop];
}
}
Object.keys(this.modifyPropsMap).forEach(p=> {
window[p]=this.modifyPropsMap[p];
});
}
/**
* 子应用卸载时,遍历当前子应用的window属性,和主应用的window快照做对比
* 如果不一致,做两步操作
* 1. 保存 不一致的window属性,
* 2. 还原window
*/
inactive() {
for (const prop in window) { // diff 差异
if (window.hasOwnProperty(prop)) {
// 将上次拍照的结果和本次window属性做对比
if (window[prop] !==this.windowSnapshot[prop]) {
// 保存修改后的结果
this.modifyPropsMap[prop]=window[prop];
// 还原window
window[prop]=this.windowSnapshot[prop];
}
}
}
}
}
主应用入口文件初始化应用,注册子应用,注册子应用时支持传入子应用列表, 注册子应用时需要指明以下几个主要参数:
options.prefetch此时可以选择是否预加载子应用。
options.sandbox默认情况下的沙箱可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离。qiankun提供了另外两种方式的隔离,供开发者选择:
import { registerMicroApps, start, initGlobalState } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
// 通讯
const { onGlobalStateChange, setGlobalState }=initGlobalState({
user: 'qiankun',
});
onGlobalStateChange((value, prev)=> console.log('[onGlobalStateChange - master]:', value, prev));
setGlobalState({
ignore: 'master',
user: {
name: 'master',
},
});
/**
* 设置默认进入的子应用
*/
setDefaultMountApp('/react16');
/**
* 启动应用
*/
start({
prefetch: true, // 预加载子应用
sandbox:{
strictStyleIsolation: true, // shadow dom的方式实现样式隔离
// experimentalStyleIsolation: true, //添加特殊的选择器的方式实现样式隔离
}
});
runAfterFirstMounted(()=> {
console.log('[MainApp] first app mounted');
});
子应用需要在自己的入口 js导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
function render(props) {
const { container }=props;
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
/**
* 和主应用通讯
*/
function storeTest(props) {
props.onGlobalStateChange((value, prev)=> console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true);
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
});
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log('[react16] props from main framework', props);
storeTest(props);
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
const { container }=props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
为了让主应用能正确识别微应用暴露出来的一些全局信息和开发环境下的跨域兼容,在子应用(以create-react-app出来的react项目为例)安装@rescripts/cli,并在子应用目录下新建.rescriptsrc.js,内容如下:
const { name }=require('./package');
module.exports={
webpack: (config)=> {
config.output.library=`${name}-[name]`;
config.output.libraryTarget='umd'; // 为了能通过window['app-name1']拿到子应用声明的生命周期
// webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
config.output.jsonpFunction=`webpackJsonp_${name}`;
config.output.globalObject='window';
return config;
},
devServer: (_)=> {
const config=_;
config.headers={
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback=true;
config.hot=false;
config.watchContentBase=false;
config.liveReload=false;
return config;
},
};
使用strictStyleIsolation:true方式进行样式隔离,会生成一个shadow dom,进行样式的完全隔离:
使用experimentalStyleIsolation:true的方式进行样式隔离,会在css选择器前添加特殊标识:
可以看到,qiankun会将子应用的html渲染到自定义的container中。 主应用加载的是子应用的html,在解析子应用的html的过程中遇到js和css会载框架内进行沙盒处理,完成css和js的隔离,之后下载并执行,完成整个子应用的渲染过程。
目前(2024年4月)github star 3.7k
wujie是腾讯出品。基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等
wujie跟qiankun一样,都是基于html entry加载的,但他们解析html的过程是不一样的。 qiankun是直接解析并执行js、css、html的,而wujie则是先解析html,提取出script脚本放入空的iframe中,提取出css、html放入到web components中,具体来说:
wujie接入很简单,主应用可以让开发者以组件的方式加载子应用。子应用只需要做支持跨域请求改造,这个是所有微前端框架运行的前提,除此之外子应用可以不做任何改造就可以在无界框架中运行,不过此时运行的方式是重建模式。 子应用也可以配置保活、生命周期适配进入保活模式或单例模式。
与其他框架一样,先配置子应用,
// main-react/index.js
import "react-app-polyfill/stable";
import "react-app-polyfill/ie11";
import React from "react";
import ReactDOM from "react-dom";
import WujieReact from "wujie-react";
import "./index.css";
import App from "./App";
import hostMap from "./hostMap";
import credentialsFetch from "./fetch";
import lifecycles from "./lifecycle";
import plugins from "./plugin";
const { setupApp, preloadApp, bus }=WujieReact;
const isProduction=process.env.NODE_ENV==="production";
bus.$on("click", (msg)=> window.alert(msg));
const degrade=window.localStorage.getItem("degrade")==="true" || !window.Proxy || !window.CustomElementRegistry;
/**
* 大部分业务无需设置 attrs
* 此处修正 iframe 的 src,是防止github pages csp报错
* 因为默认是只有 host+port,没有携带路径
*/
const attrs=isProduction ? { src: hostMap("//localhost:7700/") } : {};
/**
* 配置应用,主要是设置默认配置
* preloadApp、startApp的配置会基于这个配置做覆盖
*/
setupApp({
name: "react16",
url: hostMap("//localhost:7600/"),
attrs, // 子应用iframe的src
exec: true, // 预执行
fetch: credentialsFetch, // 自定义的fetch方法
plugins,
/** 子应用短路径替换,路由同步时生效 */
prefix: { "prefix-dialog": "/dialog", "prefix-location": "/location" },
/** 子应用采用降级iframe方案 */
degrade,
...lifecycles,
});
setupApp({
name: "vue3",
url: hostMap("//localhost:7300/"),
attrs,
exec: true,
alive: true, // 子应用保活,state不会丢失
plugins: [{ cssExcludes: ["https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"] }],
// 引入了的第三方样式不需要添加credentials
fetch: (url, options)=>
url.includes(hostMap("//localhost:7300/")) ? credentialsFetch(url, options) : window.fetch(url, options),
degrade,
...lifecycles,
});
if (window.localStorage.getItem("preload") !=="false") {
preloadApp({
name: "react16",
});
if (window.Proxy) {
preloadApp({
name: "vue3",
});
}
}
ReactDOM.render(<App />, document.getElementById("root"));
引入子应用的地方直接以组件式的方式引入:
import React from "react";
import hostMap from "../hostMap";
import WujieReact from "wujie-react";
import { useNavigate, useLocation } from "react-router-dom";
export default function React16() {
const navigation=useNavigate();
const location=useLocation();
const path=location.pathname.replace("/react16-sub", "").replace("/react16", "").replace("/",""); ////
const react16Url=hostMap("//localhost:7600/") + path;
const props={
jump: (name)=> {
navigation(`/${name}`);
},
};
return (
// 单例模式,name相同则复用一个无界实例,改变url则子应用重新渲染实例到对应路由
<WujieReact
width="100%"
height="100%"
name="react16"
url={react16Url}
sync={!path}
props={props}
></WujieReact>
);
}
截至目前(2024年4月)github star 5.2k
mirco-app 是京东2021年开源的一款微前端框架。它借助了浏览器对 webComponent 的支持,实现了一套微前端方案体系。并且由于 Shadow Dom 对 react 这类库的兼容性较差,便自己实现了类 Shadow Dom 的效果。与 qiankun 相比,接入更加简单。最新的版本也支持iframe实现js隔离,类似wujie。
首先micro-app实现了一个基于WebComponent的组件,并实现了类Shadow Dom 的效果,开发者只需要用<micro-app name="xx" url="xx" baseroute="/xxx/xxx">来加载子应用,整个对子应用的加载、js隔离、css隔离的逻辑都封装在了web component组件<micro-app>中,具体来说:
默认使用正则将CSS字符串切割成最小单元,每个单元包含一段CSS信息,将所有的信息整理生成CSSTree,遍历CSSTree的每个规则,添加前缀实现样式隔离。
micro-app有两种方式实现js隔离,默认是跟qiankun一样采用proxy沙箱的方式隔离, 在v1.0发布后支持了基于原生iframe的隔离方式。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Router from './router';
import microApp from '@micro-zoe/micro-app'
microApp.start()
ReactDOM.render(
<React.StrictMode>
<Router />
</React.StrictMode>,
document.getElementById('root')
);
export function MyPage () {
return (
<div>
<h1>子应用</h1>
// name:应用名称, url:应用地址
<micro-app name='my-app' url='http://localhost:3000/'></micro-app>
</div>
)
}
作者:韩国芳
来源:微信公众号:奇舞精选
出处:https://mp.weixin.qq.com/s/997pVVxdgpOH6ZsDsFAh2g
并讠果:quangneng.com/5131/
Swift 是苹果公司于2014年推出的一种新的编程语言,用于iOS、macOS、watchOS和tvOS应用程序的开发。Swift因其安全性、速度和现代性而受到开发者的喜爱。在这篇文章中,我们将从入门到进阶,通过实战探探iOS APP,来学习Swift编程。
一、Swift入门
首先,我们需要在苹果官方网站下载并安装Xcode,Xcode是苹果公司提供的IDE,用于Swift语言的开发。安装完成后,打开Xcode,创建一个新的Swift项目,即可开始Swift编程。
Swift的基础语法包括变量、数据类型、运算符、控制结构等。熟悉这些基础知识是进行Swift编程的前提。
Swift支持函数和闭包的概念。函数是一段具有特定功能的代码,而闭包则是一种匿名函数。掌握函数和闭包的定义和使用,有助于我们编写结构清晰、易于维护的代码。
Swift通过类和结构体实现面向对象编程。类和结构体都是用于封装数据和行为的数据类型,但类支持继承和多态,而结构体则不支持。了解它们的使用场景和操作方法,可以更好地设计程序。
枚举和协议是Swift语言中常用的特性。枚举用于定义一组具有共同属性和行为的值,协议则用于定义一组方法或属性。掌握枚举和协议,可以更好地组织代码,提高可维护性。
二、Swift进阶
在Swift中,错误处理是一个重要的环节。通过Error协议和do-catch语句,我们可以有效地处理程序运行过程中出现的错误。
Swift支持扩展和泛型编程。扩展用于为现有的类、结构体、枚举和协议添加新的功能,泛型则用于编写可复用的、类型安全的代码。
Swift支持多线程编程,通过DispatchQueue和OperationQueue等API实现。掌握多线程编程,可以充分利用计算机资源,提高程序性能。
三、实战探探iOS APP
探探是一款流行的社交应用,用户可以通过滑动来选择喜欢或不喜欢的人。在这个项目中,我们将使用Swift语言开发一个简化版的探探应用。
我们的简化版探探应用将具备以下功能:
(1)用户注册和登录;
(2)用户可以上传头像和填写个人信息;
(3)用户可以查看其他用户的资料,并进行喜欢或不喜欢操作;
(4)用户可以查看喜欢自己的人,并进行配对操作。
在这个项目中,我们将使用以下技术:
(1)使用UIKit构建用户界面;
(2)使用Core Data进行数据存储;
(3)使用 Alamofire 进行网络请求;
(4)使用 SwiftSoup 解析 HTML 数据。
在开发过程中,我们遵循Swift编程规范,采用面向对象编程、错误处理、多线程编程等技术,保证代码质量。同时,我们编写了详细的单元测试和文档,确保应用的稳定性和可维护性。
为了保证应用的高性能,我们采用了以下优化措施:
(1)使用懒加载和缓存技术,减少不必要的资源消耗;
(2)使用UITableView进行列表展示,提高列表的滑动性能;
(3)使用GCD进行图片异步加载,提高图片加载速度。
在项目部署阶段,我们使用Xcode进行应用的打包和发布。同时,我们使用TestFlight进行应用的测试和分发。
四、Swift高级特性
Swift使用自动引用计数(ARC)机制来管理内存,但开发者仍需理解内存管理的概念,以避免内存泄漏和野指针问题。了解Swift中的强引用、弱引用、无主引用等概念对于编写高效和安全的代码至关重要。
Swift的协议扩展允许开发者在不修改原有类的情况下,为协议提供默认实现。这一特性极大地增强了代码的复用性和灵活性。
泛型是Swift中的一个强大特性,它允许开发者编写适用于任何类型的代码。泛型函数和泛型类型能够提高代码的通用性和类型安全。
Swift虽然是一门静态语言,但它也提供了一定程度的动态性。例如,使用dynamic关键字可以使类成员动态派发,从而支持运行时检查和修改。
五、iOS设计模式与实践
Model-View-Controller(MVC)是iOS开发中最常用的设计模式。了解MVC模式的原则,能够帮助开发者更好地组织代码,实现视图、模型和控制器之间的解耦。
Model-View-ViewModel(MVVM)是MVC模式的一种变体,它通过引入ViewModel层来进一步解耦视图和模型,提高代码的可测试性和可维护性。
单例模式确保一个类只有一个实例,并提供一个全局访问点。在iOS开发中,单例模式常用于管理共享资源或提供全局服务。
依赖注入是一种设计模式,用于实现控制反转。在Swift中,依赖注入可以帮助我们减少组件之间的耦合,提高代码的可测试性和可维护性。
六、iOS应用测试与优化
单元测试是确保代码质量的关键。在Swift中,使用XCTest框架进行单元测试,可以验证代码的各个部分是否按预期工作。
UI测试用于验证应用的界面和用户交互是否符合预期。使用XCTest框架进行UI测试,可以确保应用的用户体验质量。
性能优化是iOS应用开发的重要环节。使用Instruments等工具进行性能分析,可以帮助开发者发现并解决性能瓶颈。
代码审查是提高代码质量的有效手段。通过代码审查,可以及时发现和修复代码中的问题,提高代码的可读性和可维护性。
七、项目总结与展望
通过实战探探iOS APP的开发,我们不仅学习了Swift编程语言的基础和进阶知识,还了解了iOS应用开发的最佳实践和设计模式。项目实践中,我们遇到了各种挑战,如网络请求的异步处理、用户界面的流畅度优化、数据存储的可靠性等,通过不断学习和尝试,我们逐一解决了这些问题,最终完成了一个功能完善、性能良好的社交应用。
展望未来,随着Swift语言的不断发展和iOS平台的新技术涌现,iOS应用开发将变得更加高效和强大。作为开发者,我们需要不断学习新技术,提高自己的技能水平,以适应不断变化的技术环境。Swift和iOS开发将继续为我们提供广阔的发展空间和无限的创新可能。
1、趣调查
2、“装修小能手”
前面我们研究了HTML,回顾下它是做什么的?
当我们用HTML搭建好网页的基本骨架,下面请出我们的“装修小能手”--CSS。
3、如何学习CSS?
Python大星前去探探路...
4、学习必备
● 充分利用谷歌浏览器Chrome的审查元素功能
● CSS权威网站
https://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference
1、基本问题
● CSS代码写在什么地方?
● CSS的语法规则?
2、CSS代码的书写位置
● 内部样式表
书写在style元素中,一般放在<head></head>中。
有人可能会问,能放到其他元素里吗?
答案:可以。但如果你使用内部样式表,建议放到head元素中,利于浏览器的加载渲染。
>> 举个栗子:
● 内联样式表(元素样式表)
直接书写在元素的全局属性style中
● 外部样式表
将样式书写到独立的css文件中。
【1】理由有三:
① 解决多页面样式重复的问题;
② 有利于浏览器缓存,提高页面响应速度;
③ 有利于代码分离,易阅读和维护。
【2】如何使用外部样式表:
3、CSS代码的语法
CSS语法=选择器 + 声明块
● 选择器(Selector)
CSS 选择器是CSS规则的一部分,使你能应用样式到指定元素。
① 基础选择器
① 关系选择器
选择紧跟A元素后的B元素,用+表示,选择相邻的第一个兄弟元素。
选择A元素之后的所有兄弟元素B,作用于多个元素,用~隔开
选择所有作为A元素的直接子元素B,对更深一层的元素不起作用,用>表示
选择所有被A元素包含的B元素,中间用空格隔开,在CSS使用频率高
③ 伪类选择器
选中某些元素的某种状态
1)link: 超链接未访问时的状态 2)visited: 超链接访问过后的状态 3)hover: 鼠标悬停状态 4)active:激活状态,鼠标按下状态 爱恨法则:love hate
● 声明块
出现在大括号{}中
声明块中包含很多声明(属性),每一个声明(属性)表达了某一方面的样式。
*请认真填写需求信息,我们会在24小时内与您取得联系。