作元素樣式有兩種方式,一種是操作style屬性,一種是操作className屬性,下面我們分別進行講解。
1. 操作style屬性
除了前面講解的元素內容和屬性外,對于元素對象的樣式,可以直接通過“元素對象.style.樣式屬性名”的方式操作。樣式屬性名對應CSS樣式名,但需要去掉CSS樣式名里的半字線“-”,并將半字線后面的英文的首字母大寫。例如,設置字體大小的樣式名font-size,對應的樣式屬性名為fontSize。
為了便于讀者的學習使用,下面我們通過表1列出常用style屬性中CSS樣式名稱的書寫及說明。
表1 常見的style屬性操作的樣式名
名稱 | 說明 |
background | 設置或返回元素的背景屬性 |
backgroundColor | 設置或返回元素的背景色 |
display | 設置或返回元素的顯示類型 |
fontSize | 設置或返回元素的字體大小 |
height | 設置或返回元素的高度 |
left | 設置或返回定位元素的左部位置 |
listStyleType | 設置或返回列表項標記的類型 |
overflow | 設置或返回如何處理呈現在元素框外面的內容 |
textAlign | 設置或返回文本的水平對齊方式 |
textDecoration | 設置或返回文本的修飾 |
textIndent | 設置或返回文本第一行的縮進 |
transform | 向元素應用2D或3D轉換 |
接下來,通過代碼演示如何對元素的樣式進行添加,具體示例如下。
<div id="box"></div>
<script>
var ele = document.querySelector('#box'); // 獲取元素對象
ele.style.width = '100px';
ele.style.height = '100px';
ele.style.transform = 'rotate(7deg)';
</script>
上述第4~6行代碼用于為獲取的ele元素對象添加樣式,其效果相當于在CSS中添加以下樣式。
#box {width: 100px; height: 100px; transform: rotate(7deg);}
2. 操作className屬性
在開發中,如果樣式修改較多,可以采取操作類名的方式更改元素樣式,語法為“元素對象.className”。訪問className屬性的值表示獲取元素的類名,為className屬性賦值表示更改元素類名。如果元素有多個類名,在className中以空格分隔。
接下來,通過代碼演示如何使用className更改元素的樣式。
(1)編寫html結構代碼,具體示例如下。
<style>
div {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
<body>
<div class="first">文本</div>
</body>
上述代碼中,第9行給div元素添加first類,并在style中設置了first的樣式,瀏覽器預覽效果如圖1所示。
圖1 初始效果
(2)單擊div元素更改元素的樣式,示例代碼如下。
<script>
var test = document.querySelector('div');
test.onclick = function () {
this.className = 'change';
};
</script>
上述代碼中,第2行獲取div元素存儲在test對象中。第3~5行為text對象添加onclick單擊事件,第4行執行事件處理程序使用this.className給test對象設置change類名,其中this指的是test對象。
(3)在style中添加change類,樣式代碼如下。
.change {
background-color: purple;
color: #fff;
font-size: 25px;
margin-top: 100px;
}
(4)單擊div盒子,瀏覽器預覽效果如圖2所示。
圖2 單擊后效果
執行上述代碼之后,會直接把原先的類名first修改為change,如果想要保留原先的類名,可以采取多類名選擇器的方式,修改第(2)步的第4行代碼,示例代碼如下。
this.className = 'first change';
修改之后,在控制臺查看到div元素的類已經修改成了<div class="first change">文本</div>,保留了之前的類名。
在進行項目開發的時候,可能會遇到“想找某個色值”的場景,因為顏色值一般是數字類型,沒有語義,不好全局搜索。所以就希望有一個工具,能夠快速展示工作目錄下的所有顏色,方便取值。
由于是在編寫代碼階段,所以這個工具最方便的其實是編輯器的插件。我使用VSCode插件,所以先來試試開發一個VSCode插件。
在正式開發之前,先來給這個插件取個好名字,color, see, 一想到這兩個單詞,我腦海里直接蹦出了“給你點顏色看看”的中文式表達”give you color to see”,看起來Color to See 是個好名字。
在開發之前,先來想想我們的”產品“長什么樣子,要實現什么功能。
插件要實現的目標是能夠快速找到某個色值,所以我們要將顏色可視化,色值是最終我們希望能拿到的,所以需要將項目中所有的顏色收集起來,統一展示(包含可視化、色值)。
具體功能清單如下:
UI設計如下:
WebView可以用來創建自定義的視圖,可以看成是VSCode內部的 Iframe 容器,可以渲染任何HTML, 通過消息傳遞實現通信,所以具備非常強大的頁面渲染和交互能力。
VSCode內部提供了三個API可以創建WebView:
創建一個編輯器面板。即時的,關閉編輯器就銷毀了,主要用于那些不需要持久化狀態(在關閉后不需要恢復)的場景,示例代碼如下:
const panel = vscode.window.createWebviewPanel(
'webviewId', // Webview 的標識符
'My Webview', // 面板標題
vscode.ViewColumn.One, // 面板顯示在哪個編輯器列中
{ enableScripts: true } // 額外的 Webview 選項
);
panel.webview.html = "<html><body><h1>Hello, Webview!</h1></body></html>";
定義如何序列化和反序列化 WebView 面板的狀態,這樣即使在 VSCode 重啟后,這個 WebView 面板的狀態也可以被恢復。所以這種WebView,對于那些需要保留用戶狀態或信息的面板來說非常有用。
class MyWebviewPanelSerializer implements vscode.WebviewPanelSerializer {
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
// 重新設置 Webview 的內容等
webviewPanel.webview.html = "<html><body><h1>Restored Webview</h1></body></html>";
}
}
context.subscriptions.push(vscode.window.registerWebviewPanelSerializer('webviewId', new MyWebviewPanelSerializer()));
創建一個編輯器面板。持久的視圖,常駐在側邊欄(Sidebar)和面板(Panel)上
class MyWebviewProvider implements vscode.WebviewViewProvider {
resolveWebviewView(webviewView: vscode.WebviewView) {
webviewView.webview.html = "<html><body><h1>Hello, Sidebar Webview!</h1></body></html>";
}
}
const provider = new MyWebviewProvider();
context.subscriptions.push(vscode.window.registerWebviewViewProvider('myWebview', provider));
考慮我們插件的使用場景一般是沒有UI稿的后臺開發,使用次數不會很頻繁,所以插件的視圖不需要持久,所以可以選擇用createWebviewPanel創建一個編輯器面板來承載界面。
通過官方文檔給的腳手架命令,開始初始化我們的插件項目。目錄結構如下:
├─ .eslintrc.json
├─ .gitignore
├─ .vscode
│ ├─ extensions.json
│ ├─ launch.json
│ ├─ settings.json
│ └─ tasks.json
├─ .vscode-test.mjs
├─ .vscodeignore
├─ .yarnrc
├─ CHANGELOG.md
├─ README.md
├─ package.json
├─ src
│ ├─ extension.ts
│ ├─ test
│ │ └─ extension.test.ts
├─ tsconfig.json
├─ vsc-extension-quickstart.md
├─ webpack.config.js
├─ yarn-error.log
└─ yarn.lock
在package.json文件中,可以看到main配置是dist文件夾下的extension.js文件,這是我們項目的入口文件,是Webpack把src/extentions.ts作為入口文件編譯過來的
看package.json的script配置,發現可以運行yarn watch命令進行熱更新,也就是實時把改動的代碼編譯成可執行的JS文件。
運行完yarn watch后,通過按“F5”或者“運行->啟動調試”,運行我們的插件,這時候VSCode會打開一個新的窗口,快捷鍵ctrl + shift + p輸入hello world
最后,點擊執行這個命令,可以看到右下角彈出了歡迎標語。如下圖所示:
如果修改了代碼,可以F5刷新debug窗口,也可以在debug窗口上使用快捷鍵command + R刷新,和在瀏覽器刷新網頁一樣。
以上就是 VSCode 插件開發起步的過程。
在package.json文件的commands屬性上新增extension.colorToSee,這個命令是使用我們插件的起點,所以務必寫個好title,為了讓插件國際化,我的title設置為"ColorToSee: Show colors of the working directory in a webview panel"
"commands": [
{
"command": "extension.colorToSee",
"title": "ColorToSee: Show colors of the working directory in a webview panel"
}
],
這個命令的執行回調函數在extension.ts文件上,重點代碼如下:
vscode.commands.registerCommand(COMMAND_NAME, () => {
registerWebviewViewProvider(context);
})
在這串代碼中,我們把具體的實現邏輯封裝在registerWebviewViewProvider方法上,接下來我們可以只關注這個方法的實現。
使用WebView API createWebviewPanel來創建一個自定義的HTML頁面,基本的代碼塊如下所示:
const panel = vscode.window.createWebviewPanel(
CatCodiconsPanel.viewType,
"Cat Codicons",
column || vscode.ViewColumn.One
);
panel.webview.html = _getHtmlForWebview(panel.webview, extensionUri);
function _getHtmlForWebview(webview: vscode.Webview) {
// Use a nonce to only allow specific scripts to be run
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Use a content security policy to only allow loading images from https or from our extension directory, and only allow scripts that have a specific nonce. -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your View</title>
</head>
<body>
<h1>Hello from Your View!</h1>
</body>
</html>`;
}
function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
在registerWebviewViewProvider方法中,我們將基于上述代碼做些改造,大致的框架如下:
const registerWebviewViewProvider = (context: vscode.ExtensionContext) => {
const provider = new ViewProvider(context.extensionUri, config);
const panel = vscode.window.createWebviewPanel(
ViewProvider.viewType, // Webview 的標識符
PANEL_TITLE, // 面板標題
vscode.ViewColumn.One, // 面板顯示在哪個編輯器列中
{
enableScripts: true
} // 額外的 Webview 選項
);
provider.resolveWebviewView(panel as unknown as vscode.WebviewView);
context.subscriptions.push(panel);
};
在這段代碼中,我們把頁面信息維護在ViewProvider這個類上。在這個類上,我們要實現頁面的渲染和更新。
頁面的渲染機制可以借用React 框架的 render(state) 思想,這個思想基于函數式編程的原則,其核心是將UI視為狀態的函數,UI的每一次更新可以看作是一個狀態轉換的結果。
本插件的頁面構成比較簡單,為了方便,可以直接使用原生JS和HTML開發。頁面的模塊主要分為以下4個部分:
整個數據驅動式的頁面渲染如下所示:
<body>
${generateMainDiv(this.colorInfos)}
</body>
首先,我們定義colorInfos狀態,這個變量存儲了當前工作區所有顏色信息,包括該顏色的所在文件的位置和色值,TS定義如下所示:
export type ColorItem = {
/** 顏色值起始位置 */
start: number;
/** 顏色值結束位置 */
end: number;
/** 顏色值 */
color: string;
/** 顏色值所在的文件路徑 */
file: string;
};
”顏色網格“是我們整個頁面主要的功能模塊,所以我們重點介紹這個功能的渲染函數,根據colorInfos,一個能根據這個狀態生成HTML的函數可以簡化成這樣:
function generateMainDiv(colors) {
return colors.map(info => `<div style="color: ${info.color}" data-colorItem="${encodeURIComponent(
JSON.stringify(item)
)}">${info.color}</div>`).join('');
}
其中自定義屬性data-colorItem存儲了整個顏色塊的所有信息。
把html元素賦值給WebView的html屬性,就可以實現頁面的掛載。
this._view.webview.html = this._getHtmlForWebview(this._view.webview);
function _getHtmlForWebview(webview: vscode.Webview) {
// Use a nonce to only allow specific scripts to be run
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Use a content security policy to only allow loading images from https or from our extension directory, and only allow scripts that have a specific nonce. -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your View</title>
</head>
<body>
${generateMainDiv(this.colorInfos)}
</body>
</html>`;
}
上面描述的數據驅動式框架比較簡單,還沒有涉及到本項目的難點實現,如果構造colorInfos數據,是本項目的難點之一,本節主要講這塊內容。
在項目開發中,比較常見的顏色格式分為RGB, Hex, HSL和顏色關鍵字(Named Colors)這幾種。下面我們來分析這幾種顏色在CSS中的語法。
從MDN官網發現rgb()的寫法分為絕對和相對格式,我們平時熟悉的寫法是這樣的:rgb(x, y, z),其中x、y、z是從0到255的整數,屬于絕對格式的一種。
為了讓項目能盡快先落地,我們此處只分析“絕對格式”的場景。絕對格式的寫法如下:
rgb(R G B[ / A])
其中R G B的值可以為0-255的整數,或者0%-100%的百分比;A的值為0到1(或者0%-100%),表示顏色的透明度信息。如果有A值時,需要使用 / 。
我們經常見到的一種寫法是R G B通過逗號隔開,這種寫法其實是一種過時的寫法,但是考慮到大部分人都在用,匹配CSS RGB顏色的時候也應該考慮到。rgba()語法也是一種過時的寫法,同樣本次也會考慮這種顏色格式的匹配。
CSS hsl() 的語法和rgb()是一致的,這里不在贅述,需要請移至MDN官網查看。
16進制Hex的色值寫法分為以下幾種:
#RGB // The three-value syntax
#RGBA // The four-value syntax
#RRGGBB // The six-value syntax
#RRGGBBAA // The eight-value syntax
其中R G B的值為0到ff。
在CSS中,一些常用顏色可以使用預定義的關鍵字來表示,例:red、blue、green等,這些就是顏色關鍵詞。完整的標準關鍵詞可以在MDN官網找到。
了解了顏色在項目中的多種寫法,下面考慮如何匹配代碼中的顏色。常見的方案分為兩種:
前者實現比較簡單且通用性大,因此本插件選擇通過正則語法,針對不同的顏色格式,定制不同的匹配策略。
編寫一個基本的正則表達式來匹配顏色代碼其實不難,但是如果想提高要求,確保寫出來的正則表達式既準確又具有良好的防御性,能夠考慮到各種邊緣情況以及性能優化,這并不容易。
為了實現這一點,此處我借鑒了另外一個插件Color Highlight的實現。這個插件是我目前在使用的比較好用的插件,在開發自己的插件之前,我從沒想過它是如何實現的,但是我熟悉它的功能:在編寫代碼的時候,可以高亮顯示當前當前編輯器中的顏色格式。要實現的功能和我的有相似之處,所以了解這個插件的實現應該對于完成自己的插件很有幫助。
事實確實是這樣的,Color Highlight插件給出了一系列針對不同顏色格式的匹配策略,如下圖所示。
基于它的實現,下面分析了針對不同顏色格式的正則表達式解析。
const colorRegex = /((rgb|hsl)a?(\s*[\d]*.?[\d]+%?\s*(?<commaOrSpace>\s|,)\s*[\d]*.?[\d]+%?\s*\k<commaOrSpace>\s*[\d]*.?[\d]+%?(\s*(\k<commaOrSpace>|/)\s*[\d]*.?[\d]+%?)?\s*))/gi;
整個正則表達式是一個全局不區分大小寫的匹配(由結尾的 gi 標志指定),用于在文本中查找所有匹配的顏色值。主要分為兩部分:格式名稱(值),其中格式名稱分為三種情況,如下圖所示,分別為rgb,hsl,rgba, hsla。后半部分主要是匹配它們的不同書寫方式,包含數字值、逗號或空格分割。具體解析如下:
收集MDN官網給出的顏色關鍵字,寫一個簡單的正則,下面是列舉了部分色值的正則匹配:
(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)
const colorHex =
/.?((?:#|\b0x)([a-f0-9]{6}([a-f0-9]{2})?|[a-f0-9]{3}([a-f0-9]{1})?))\b/gi;
這個正則表達式用來匹配 CSS 中的十六進制顏色值,同時也匹配十六進制顏色值的簡寫形式,并且考慮到可能的透明度值(RGBA格式)。下面逐部分解釋這個正則表達式:
了解了不同顏色格式的匹配規則后,顏色的獲取思路其實很簡單:遍歷每個文件,獲取每個文件的文本字符串,通過對應的正則匹配策略去匹配?;镜拇a如下所示:
從產品的使用場景看來,本插件所關注的顏色格式比較簡單:一些基本的rgb, hex, hsl,單詞的顏色可以不考慮,因為用不上,能用上我都可以直接匹配了,因此我們只關注沒有語義、且常用的顏色格式。
最終,針對本插件,可以總結出下面三種策略:
this.strategies = [findColorFunctionsInText,findHexRGBA];
for (const file of files) {
try {
const document = await vscode.workspace.openTextDocument(file);
const instance = await this.findOrCreateInstance(document);
colorsInfos.push(await instance.getColorInfo());
} catch {
continue;
}
}
getColorInfo(document = this.document) {
const text = this.document.getText();
const version = this.document.version.toString();
const file = this.document.uri.fsPath; // file path
const result = await Promise.all(this.strategies.map((fn) => fn(text)));
return resolveResult(result); // 顏色解析,根據colorInfos的數據格式進行數據解析
}
顏色的更新場景主要有以下幾種情況:
針對這三種情況,在產品設計上,可以設置一個“刷新按鈕“按鈕,點擊該按鈕,拉取最新的顏色信息。本章節主要從消息通信和更新機制兩個角度討論本插件的實現。
在頁面上點擊按鈕,然后執行對應的事件,這點的實現涉及到VSCode插件中Extension 和 Webview 的通信,主要是通過postMessage和onDidReceiveMessage實現消息的發送與接收。
Extension 里通過vscode.postMessage發送消息:
// 刷新
refreshBtn.addEventListener('click', () => {
// 如果已經在Loading, 無需發送message
if (refreshBtn.classList.contains('btn--loading')) {
return;
}
refreshBtn.classList.add('btn--loading');
vscode.postMessage({ command: 'refresh' });
});
webviewView.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case 'refresh':
const prom = () => this.doUpdateWebView()
prom().finally(() => {
webviewView.webview.postMessage({
command: 'refreshEnd'
});
});
break;
}
});
在Extension接受到需要更新的消息后,需要執行對應的更新機制,這個實現主要封裝在doUpdateWebView方法中。更新機制主要實現的就是更新colorInfos,并重新執行渲染函數以更新UI。
為了讓插件的第一個版本快速上線,文件新增、刪除場景的更新我們可以重新掃描一下所有文件;文件編輯的場景容易定位具體文檔,所以可以按需更新。
下面給出了本項目的更新實現,其中整個了頁面初始化的渲染,因為這個場景的邏輯和文件新增、刪除重復。
private async doUpdateWebView() {
try {
if (
this.type === 'init' ||
this.type === 'add' ||
this.type === 'delete'
) {
await this.initDataView();
return Promise.resolve();
}
// 顏色變更:text change
// 收集變更的document,局部更新顏色視圖
for (let index = 0; index < this.instanceMap.length; index++) {
const instance = this.instanceMap[index];
// 如果頁面更改了
if (instance.changed) {
const colorDocumentItem = await instance.getColorInfo();
this.colorMapArray[index] = colorDocumentItem;
// 恢復
instance.changed = false;
}
}
// 更新顏色信息
this.colorInfos = updateColorInfosByMap(this.colorMapArray);
// 更新視圖
this._view.webview.html = this._getHtmlForWebview(this._view.webview);
return Promise.resolve();
} catch {
return Promise.reject();
}
}
代碼示意圖如下所示:
并不是項目中所有文件都有顏色值,所以為了讓插件能夠有效地找到有用的色值,我們需要給插件增加文件類型配置項,用來定義哪些文件應該在文件掃描過程中包含或排除。
include 和 exclude 這兩個配置項在很多前端開發相關的工具和配置文件中非常常見,如 webpack.config文件、tsconfig文件、babel.config文件。為了降低插件使用者的學習曲線,本插件也選擇使用 include 和 exclude 來指定掃描的文件類型。
下面是本插件默認的配置屬性。
"color-to-see.findFilesRules": {
"default": {
"include": [
"**/*.js",
"**/*.jsx",
"**/*.tsx",
"**/*.css",
"**/*.less",
"**/*.sass",
"**/*.html",
"**/*.vue"
],
"exclude": [
"**/node_modules/**",
"**/dist/**",
".git"
]
},
掃描的文件類型主要關注實際開發中包含色值定義的文件類型,比如樣式文件、HTML文件或者JS文件。排除不需要掃描或處理的文件夾,如node_modules,dist,.git文件等。include和exclude的值使用 Glob Pattern 語法來指定哪些文件被包括或排除。
在Extension中,這套配置的使用原理如下:
config = vscode.workspace.getConfiguration(EXTENSION_NAME);
const findFilesUsingConfig = async (config: Config) => {
const { include, exclude } = config.findFilesRules;
const includePattern = `{${include.join(',')}}`;
const excludePattern = `{${exclude.join(',')}}`;
try {
const files = await vscode.workspace.findFiles(
includePattern,
excludePattern
);
return files;
} catch {
return [];
}
};
:Color to See - Visual Studio Marketplace
盡管插件的功能大致已經實現了,但是本插件還是有一些局限性,以及后續可以優化的地方
插件需要掃描整個工作區的所有文件來查找顏色值,盡管增加了文件類型限制少掃描一些文件,但是剩下的文件掃描還是會消耗大量的計算資源。
顏色展示必須等待文件全部掃描完,長時間的掃描和等待會影響用戶體驗
實現異步掃描機制,一旦掃描到新的顏色值,就立即在UI中展示,而不是等待所有掃描完成。
顏色值跳轉功能的實用性其實比較低,而且在本插件中,如果一個顏色在多個地方出現,我只會記錄第一個位置。因此,本插件的使用過程中,用戶應該更加關注于顏色值本身,而不是其在代碼中的具體位置。
除此之外,顏色跳轉到具體某個文件后,當前視圖的位置沒有滾動到對應的高度。
增加一個配置項讓用戶可以自行決定是否啟用顏色值跳轉功能。默認關閉。
本文基于一個場景問題,介紹了VSCode插件的開發過程,總的來說,這個項目算是一次很有意思的獨立開發體驗。在這個過程中,可以總結出以下幾個比較重要的點:
作者:蘭燕平
來源-微信公眾號:Goodme前端團隊
出處:https://mp.weixin.qq.com/s/WGV70jVODC_IVmh7dM4Sag
幾天,我手里的一個項目需要將富文本的所有 html 標簽全部刪除,得到純文本后再存儲到數據庫中。在一系列得搜索操作之后,我找到了實現這個目的的幾種方法,在這里我分享給大家,當你遇到同樣的情況興許也能用的上。
這個方法是從文本中去除 html 標簽最簡單的方法。它使用字符串的方法 .replace(待替換的字符串,替換后的字符串) 將 HTML 標簽替換成空值。 /g 是表示替換字符串所有匹配的值,即字符串中所有符合條件的字符都將被替換。
這個方法的缺點是有些 HTML 標簽不能被剔除,不過它依然很好用。
這種方法是完成該問題的最有效的方法。創建一個臨時 DOM 并給他賦值,然后我們使用 DOM 對象方法提取文本。
html-to-text 這個包的功能很全了,轉換也有許多的選項比如:wordwrap, tags, whitespaceCharacters , formatters 等等。
安裝:
npm install html-to-text
使用:
最后感謝閱讀,如果此文對您有幫助,請點贊或添加關注。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。