前端架構是為了在解決單體應用在一個相對長的時間跨度下,由于參與的人員、團隊的增多、變遷,從一個普通應用演變成一個巨石應用(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小時內與您取得聯系。