您好,登錄后才能下訂單哦!
翻譯自:https://github.com/dojo/framework/blob/master/docs/en/styling/supplemental.md
Dojo 部件最適合作為簡單的組件,每個組件處理單一職責。它們應該盡可能的封裝和模塊化,以提高可重用性,同時避免與應用程序使用的其他組件出現沖突。
可以使用常規的 CSS 為部件設置樣式,但是為了達到封裝和復用的目標,每個部件應該維護各自的 CSS 模塊(CSS module),該模塊與部件的源代碼存放在各自的文件中。這樣就可以獨立地設置各部件的樣式,而不會與應用程序其他地方使用的類名沖突。
Dojo 界定出以下幾類樣式,每一類都代表了企業 web 應用程序中的樣式需關注的不同方面和粒度:
theme
中間件中的 theme.classes(css)
API,傳入需要主題化的 CSS,并在渲染時使用返回的類名。部件的使用者可以按需覆寫部分或所有類。variables.css
文件,供其他樣式導入和引用如上述列表所示,無論是跨整個應用程序,還是單個樣式類中的單條樣式規則,Dojo 為應用程序開發者提供了幾種互相補充的機制來提供或重寫 CSS 樣式類。
Dojo 借助 CSS Modules,既提供了 CSS 的所有靈活性,又引入了本地化樣式類的額外優勢,防止大型應用程序中無意間的樣式沖突。Dojo 也為每個 CSS 模塊生成類型定義文件,允許部件用與導入其他 TypeScript 模塊相似的方式來導入 CSS 模塊,并以類型安全的方式引用 CSS 類名,同時在設計期間可以使用 IDE 自動完成功能。
部件的 CSS 模塊文件應該使用 .m.css
擴展名,并約定 CSS 模塊的文件名要與關聯的部件名保持一致。具有此擴展名的文件會被當作 CSS 模塊來處理,而不是普通的 CSS 文件。
以下是部件的 CSS 模塊文件:
src/styles/MyWidget.m.css
.myWidgetClass {
font-variant: small-caps;
}
.myWidgetExtraClass {
font-style: italic;
}
在對應的部件中使用此樣式,如下所示:
src/widgets/MyWidget.ts
import { create, tsx } from '@dojo/framework/core/vdom';
import * as css from '../styles/MyWidget.m.css';
const factory = create();
export default factory(function MyWidget() {
return <div classes={[css.myWidgetClass, css.myWidgetExtraClass]}>Hello from a Dojo widget!</div>;
});
在構建的應用程序中查看示例部件中的 CSS 類時,它們不會直接包含 myWidgetClass
和 myWidgetExtraClass
,而是經過混淆處理的 CSS 類名,類似于 MyWidget-m__myWidgetClass__33zN8
和 MyWidget-m__myWidgetExtraClass___g3St
。
混淆后的類名專用于 MyWidget
元素,而這是由 Dojo 的 CSS 模塊化構建流程決定的。有了這種機制,則同一個應用程序的其他部件也可以使用 myWidgetClass
類名,即使具有不同的樣式規則,也不會在每組樣式間出現任何沖突。
警告: 混淆處理的 CSS 類名是不穩定的,可能會隨著應用程序的構建而更改,所以開發人員不能顯式地引用它們(例如試圖在應用程序的其他位置定位一個元素)。
Dojo 可以使用現代的 CSS 特性,例如自定義屬性和 var()
,來提取和集中管理應用程序中的通用樣式屬性。
不必在每個部件的 CSS 模塊中為顏色或字體設置相同的值,而是通過提取自定義屬性,在每個 CSS 模塊中引用該屬性名,然后在集中一處的 CSS :root
偽類中設置值。這種隔離更易于維護跨整個應用程序的公共樣式。
例如:
src/themes/variables.css
:root {
/* different sets of custom properties can be used if an application supports more than one possible theme */
--light-background: lightgray;
--light-foreground: black;
--dark-background: black;
--dark-foreground: lightgray;
--padding: 32px;
}
src/themes/myDarkTheme/MyWidget.m.css
@import '../variables.css';
.root {
margin: var(--padding);
color: var(--dark-foreground);
background: var(--dark-background);
}
注意,在一個頁面中,:root
偽類是全局的,但是因為 Dojo 使用了 CSS 模塊,則可能會在應用程序的多處指定 :root
屬性。但是 Dojo 無法保證 CSS 模塊的處理順序,因此為了確保 :root
中屬性的一致性,建議在應用程序的代碼中只有一處 :root
定義,統一放在 variables.css
文件中。這個集中存放的變量文件是一個常規的 CSS 文件(不是一個 CSS 模塊),當 CSS 模塊需要使用自定義屬性值時,可以使用 @import
導入。
Dojo 默認的構建流程按原樣將自定義屬性輸出到應用程序的樣式表中。對于最新的瀏覽器來說,這樣做沒有問題;但當使用的瀏覽器沒有實現 CSS 自定義屬性標準(如 IE)時,就會出現問題。為了解決這個問題,可以使用遺留模式(dojo build app --legacy
)來構建應用程序,這種情況下,Dojo 會在構建期間解析自定義屬性的值,并復制到輸出的樣式表中。一個值將包含原來的 var()
引用,第二個值是專為舊版瀏覽器解析的值,當無法處理 var()
時就使用解析后的值。
將主題應用到 Dojo 部件后,部件的默認樣式類會完全被主題提供的樣式類覆蓋。當只需要通過主題修改樣式類中的一部分屬性,而其余屬性依然使用默認值時,就會出現問題。
Dojo 應用程序中的 CSS 模塊文件可以使用 composes:
功能將樣式從一個類選擇器應用到另一個類選擇器。當通過調整現有的主題來創建一個新主題時,或者在單個主題中提取通用的樣式屬性時(注意,提取單個屬性的值是更標準的做法是 CSS 自定義屬性),這個功能是很有用的。
警告: composes:
功能可能會比較脆弱,例如當擴展一個不受當前應用程序控制的第三方主題時。第三方主題所做的任何更改,都可能會破壞基于 composes
功能的應用程序主題,且這樣的破壞很難定位和解決。
但是,在大型應用程序中仔細使用此功能會很有用。比如,集中管理一組公共屬性:
src/themes/common/ButtonBase.m.css
.buttonBase {
margin-right: 10px;
display: inline-block;
font-size: 14px;
text-align: left;
background-color: white;
}
src/themes/myBlueTheme/MyButton.m.css
.root {
composes: buttonBase from '../common/ButtonBase.m.css';
background-color: blue;
}
由于 Dojo 應用程序中的樣式主要是針對單個部件的,因此不需要使用復雜的選擇器。在 Dojo 中為應用程序設置樣式時應盡可能簡單,開發人員可通過以下幾條簡單的建議做到這一點:
classes
或 theme
屬性。!important
Dojo 應用程序需要一種方法,來為所有部件展示一致的外觀,這樣用戶就可以整體地把握和使用應用程序功能,而不是認為將東拼西湊的元素混搭在網頁中。這通常要根據公司或產品的營銷主題來指定顏色、布局或字體等實現的。
考慮讓部件支持主題需要做兩方面的準備:
theme
中間件,const factory = create({ theme })
theme.classes(css)
返回的一個或多個部件樣式類。按慣例,當開發的部件需要分發時,還需要考慮第三點要求(Dojo 部件庫中的部件都遵循此約定):
root
的樣式類。這樣當在自定義主題中覆寫第三方可主題化部件的樣式時,就能以一致的方式定位到頂層節點。theme
中間件是從 @dojo/framework/core/middleware/theme
模塊中導入的。
theme.classes
方法theme.classes
將部件的 CSS 類名轉換應用程序或部件的主題類名。
theme.classes<T extends ClassNames>(css: T): T;
theme
中間件屬性theme
(可選)
classes
(可選)
下面是一個可主題化部件的 CSS 模塊文件:
src/styles/MyThemeableWidget.m.css
/* requirement 4, i.e. this widget is intended for wider distribution,
therefore its outer-most VDOM element uses the 'root' class: */
.root {
font-family: sans-serif;
}
/* widgets can use any variety of ancillary CSS classes that are also themeable */
.myWidgetExtraThemeableClass {
font-variant: small-caps;
}
/* extra 'fixed' classes can also be used to specify a widget's structural styling, which is not intended to be
overridden via a theme */
.myWidgetStructuralClass {
font-style: italic;
}
在相應的可主題化的部件中使用這些樣式:
src/widgets/MyThemeableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import * as css from '../styles/MyThemeableWidget.m.css';
/* requirement 1: */
const factory = create({ theme });
export default factory(function MyThemeableWidget({ middleware: { theme } }) {
/* requirement 2 */
const { root, myWidgetExtraThemeableClass } = theme.classes(css);
return (
<div
classes={[
/* requirement 3: */
root,
myWidgetExtraThemeableClass,
css.myWidgetExtraThemeableClass
]}
>
Hello from a themed Dojo widget!
</div>
);
});
部件也能導入和引用多個 CSS 模塊,除了本指南的其它部分介紹的基于 CSS 的方法(CSS 自定義屬性 和 CSS 模塊化組合功能)之外,這提供了另一種通過 TypeScript 代碼來提取和復用公共樣式屬性的方法。
擴展上述示例:
src/styles/MyThemeCommonStyles.m.css
.commonBase {
border: 4px solid black;
border-radius: 4em;
padding: 2em;
}
src/widgets/MyThemeableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import * as css from '../styles/MyThemeableWidget.m.css';
import * as commonCss from '../styles/MyThemeCommonStyles.m.css';
const factory = create({ theme });
export default factory(function MyThemeableWidget({ middleware: { theme } }) {
const { root } = theme.classes(css);
const { commonBase } = theme.classes(commonCss);
return <div classes={[root, commonBase, css.myWidgetExtraThemeableClass]}>Hello from a themed Dojo widget!</div>;
});
部件的使用者可以將一個有效的主題傳給部件實例的 theme
屬性,來重寫特定部件實例的主題。當需要在應用程序的不同部分以多種方式顯示給定的部件時,這個功能就能派上用場。
例如,在可主題化部件示例的基礎上構建:
src/themes/myTheme/styles/MyThemeableWidget.m.css
.root {
color: blue;
}
src/themes/myThemeOverride/theme.ts
import * as myThemeableWidgetCss from './styles/MyThemeableWidget.m.css';
export default {
'my-app/MyThemeableWidget': myThemeableWidgetCss
};
src/widgets/MyApp.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import MyThemeableWidget from './src/widgets/MyThemeableWidget.tsx';
import * as myThemeOverride from '../themes/myThemeOverride/theme.ts';
const factory = create();
export default factory(function MyApp() {
return (
<div>
<MyThemeableWidget />
<MyThemeableWidget theme={myThemeOverride} />
</div>
);
});
此處,渲染了兩個 MyThemeableWidget
實例,如果指定了應用程序范圍的主題,則第一個部件會使用此主題,否則使用部件的默認樣式。相比之下,第二個部件始終使用 myThemeOverride
中定義的主題。
主題機制提供了一種簡便的方式,為應用程序中的每個部件統一應用自定義樣式,但當用戶希望為給定的部件實例應用額外的樣式時,在這種場景下主題機制就不夠靈活。
可以通過可主題化部件的 classes
屬性來傳入額外的樣式類。這些樣式類是追加的,不會重寫部件已有的樣式類,它們的目的是對已經存在的樣式進行細粒度的調整。提供的每一組額外的樣式類都需要按照兩個級別的 key 進行分組:
例如,額外的樣式類屬性的類型定義為:
type ExtraClassName = string | null | undefined | boolean;
interface Classes {
[widgetThemeKey: string]: {
[baseClassName: string]: ExtraClassName[];
};
}
作為一個提供額外樣式類的示例,下面調整 Dojo combobox 實例,以及其中的子部件 text input。此操作會將 combobox 使用的 text input 控件的背景色以及其自身面板的背景色改為藍色。combobox 控件面板中的下拉箭頭也會變為紅色:
src/styles/MyComboBoxStyleTweaks.m.css
.blueBackground {
background-color: blue;
}
.redArrow {
color: red;
}
src/widgets/MyWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import ComboBox from '@dojo/widgets/combobox';
import * as myComboBoxStyleTweaks from '../styles/MyComboBoxStyleTweaks.m.css';
const myExtraClasses = {
'@dojo/widgets/combobox': {
controls: [myComboBoxStyleTweaks.blueBackground],
trigger: [myComboBoxStyleTweaks.redArrow]
},
'@dojo/widgets/text-input': {
input: [myComboBoxStyleTweaks.blueBackground]
}
};
const factory = create();
export default factory(function MyWidget() {
return (
<div>
Hello from a tweaked Dojo combobox!
<ComboBox classes={myExtraClasses} results={['foo', 'bar']} />
</div>
);
});
注意,部件的作者負責顯式地將 classes
屬性傳給所有的要使用樣式類的子部件,因為 Dojo 本身無法將這個屬性注入給或自動傳給子部件。
要為應用程序中所有可主題化的部件指定一個主題,可在應用程序頂層部件中使用 theme
中間件中的 theme.set
API。要設置默認的或初始的主題,則在調用 theme.set
之前要先使用 theme.get
進行確認。
例如,為應用程序設置一個初始主題:
src/App.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import myTheme from '../themes/MyTheme/theme';
const factory = create({ theme });
export default factory(function App({ middleware: { theme }}) {
// if the theme isn't set, set the default theme
if (!theme.get()) {
theme.set(myTheme);
}
return (
// the application's widgets
);
});
有關導入的 myTheme
結構說明,請參考編寫主題。
請注意,使用可主題化的部件時,如果沒有顯示指定主題(例如,沒有使用 theme.set
設置一個默認主題,也沒有顯式地重寫部件實例的主題或樣式類),則每個部件都使用默認的樣式規則。
如果使用一個完全獨立分發的主題(/learn/styling/working-with-themes#distributing-themes),應用程序還需要將囊括主題的 index.css
文件集成到自身的樣式中來。在項目的 main.css
文件中導入。
src/main.css
@import '@{myThemePackageName}/{myThemeName}/index.css';
與之相比,另一種使用外部構建主題的部分內容的方法是通過主題組合功能(/learn/styling/working-with-themes#composing-off-dojo-themes)實現的。
theme
中間件中的 .set(theme)
函數用于在整個應用程序級別更改當前激活的主題。為 .set
傳入所需的主題,這將讓應用程序樹中所有可主題化的部件失效,并使用新的主題重新渲染。
src/widgets/ThemeSwitcher.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import myTheme from '../themes/MyTheme/theme';
import alternativeTheme from '../themes/MyAlternativeTheme/theme';
const factory = create({ theme });
export default factory(function ThemeSwitcher({ middleware: { theme } }) {
return (
<div>
<button
onclick={() => {
theme.set(myTheme);
}}
>
Use Default Theme
</button>
<button
onclick={() => {
theme.set(alternativeTheme);
}}
>
Use Alternative Theme
</button>
</div>
);
});
Dojo 的主題框架使用“部件主題 key” 概念將重寫的樣式與對應部件的相關樣式關聯。覆蓋的樣式通常在主題中指定;但如果需要,也可以直接傳給 theme
中間件的 classes
屬性。
一個部件的主題 key 的確切格式為:
{package-name}/{widget-css-module-name}
其中 package-name
是項目 package.json
中 name
屬性的值,widget-css-module-name
是部件使用的主 CSS 模塊的文件名(不包括 .m.css
擴展名)。
給定如下項目:
package.json
{
"name": "my-app"
}
遵循部件的 CSS 模塊命名規范時,一個 src/widgets/MyWidget.ts
使用的 CSS 模塊名類似于 src/styles/MyWidget.m.css
。因此 MyWidget
的主題 key 為:
my-app/MyWidget
此處,部件的名稱與其 CSS 模塊文件的名稱相同,但是開發人員要注意,不要將部件的主題 key 誤認為就是部件的 TypeScript 類名。
再看第二個部件,沒有遵循 CSS 模塊的命名規范,如 src/widgets/BespokeWidget.ts
使用的 CSS 模塊為 src/styles/BespokeStyleSheet.m.css
,則部件的主題 key 應改為:
my-app/BespokeStyleSheet
主題就是一個 TypeScript 模塊,會導出一個默認對象,其中將部件主題 key 映射到導入的類型化的 CSS 模塊上。主題中的 CSS 模塊與部件直接使用的常規模塊相同。一旦應用程序應用了主題,則主題定義對象中的主題 key 標識的每一個部件的樣式,都會被主題 key 對應的 CSS 模塊中的樣式覆蓋。
以下是 MyWidget
部件完整主題中的一個簡單示例(使用默認的 CSS 模塊 MyWidget.m.css
),位于 my-app
項目中:
src/themes/myTheme/styles/MyWidget.m.css
.root {
color: blue;
}
src/themes/myTheme/theme.ts
import * as myThemedWidgetCss from './styles/MyWidget.m.css';
export default {
'my-app/MyWidget': myThemedWidgetCss
};
此處,MyWidget
遵循命名規范,將主樣式類命名為 root
,這樣 myTheme
就可以被 src/themes/myTheme/styles/MyWidget.m.css
CSS 模塊中的 root
類覆蓋掉。
通過主題 key my-app/MyWidget
,主題將新的 root
樣式類關聯到 MyWidget
上。當應用 myTheme
主題后,MyWidget
會將其顏色設置為藍色,且不會再接收其初始 CSS 模塊的 root
類中定義的其他樣式。
應用程序的主題可能需要包含第三方部件使用的樣式,比如Dojo 自帶部件庫中提供的樣式。
@dojo/cli-create-theme
中提供了一些工具,使用 dojo create theme
CLI 命令,能為第三方部件快速生成主題腳手架。可通過以下方式在應用程序中安裝:
npm install --save-dev @dojo/cli-create-theme
然后在項目根目錄下按如下方式使用:
dojo create theme -n {myThemeName}
運行此命令,會在詢問兩個問題后開始創建 myThemeName
主題:
@dojo/widgets
。本命令會繼續詢問更多的包,直到用戶結束此操作。命令成功執行后,會在當前項目中創建幾個文件:
src/themes/{myThemeName}/theme.ts
src/themes/{myThemeName}/{third-party-package}/path/to/{selectedWidget}.m.css
為所有 {selectedWidget}
創建的主題 CSS 模塊都提供了可主題化的 CSS 選擇器,然后就可以為 {myThemeName}
填充合適的樣式規則。
任何包含 theme
目錄的第三方包都是兼容的,其中既包含部件的 CSS 模塊文件(*.m.css
),也包含對應的編譯后的定義文件(*.m.css.js
- 詳情參見分發主題)。
例如:
node_modules
└── {third-party-package}
└── theme
│ {widget}.m.css
│ {widget}.m.css.js
Dojo 的 cli-build-theme
提供了一個 CLI 命令,構建的主題可分發給多個應用程序使用。它會創建出以各種不同方式使用主題所需的所有文件。
注意,當使用 dojo create theme
搭建新的主題時,并不需要使用 dojo build theme
,因為所有相關文件都已就位。這主要用于使用 @dojo/cli-build-app
或 @dojo/cli-build-widget
構建項目時來構建主題。
要使用此工具,在需要主題化的項目下安裝 @dojo/cli-build-theme
:
npm install --save-dev @dojo/cli-build-theme
然后構建主題,請運行命令,并指定一個主題名以及一個可選的發布版本號:
dojo build theme --name={myThemeName} --release={releaseVersion}
如果沒有指定 release
,則會使用 package.json
中的當前版本號。
運行該命令后,會在項目中創建一個 dist/src/{myThemeName}
文件夾,其中包含:
index.js
文件,會被導入并用于主題化應用程序或兼容的部件.m.css
)。這些文件可以通過主題組合功能直接引用,可用于基于新構建的主題來創建出自己主題的所有應用程序。assets
文件夾,包含主題文件夾中的所有字體和圖片。index.css
文件,如果要使用整個主題,則需要將其導入到應用程序的 main.css
中。{name}-{release}.js
文件,會使用全局的注冊器注冊主題(使用 <script>
標簽添加)。{name}-{release}.css
文件,使用 <link rel="stylesheet">
標簽添加。@dojo/themes
包提供了一組立即可用的主題,涵蓋了 Dojo 自帶部件庫的所有部件。可以按原樣使用主題庫,或者作為基礎組合出完整的應用程序主題。
要使用主題,在項目中安裝 @dojo/themes
,比如使用 npm i @dojo/themes
命令。然后,對于常規的 Dojo 應用程序:
在項目的 main.css
文件中導入主題的 CSS 文件:
@import '~@dojo/themes/dojo/index.css';
導入主題的 TypeScript 模塊,然后使用:
import theme from '@dojo/themes/dojo';
render() {
return w(Button, { theme }, [ 'Hello World' ]);
}
如果嘗試在 Custom elements 中使用它,則安裝完 @dojo/themes
之后:
在 index.html
中添加 Custom elements 專用的主題 CSS 文件:
<link rel="stylesheet" href="node_modules/@dojo/themes/dojo/dojo-{version}.css" />
在 index.html
中添加 Custom elements 專用的主題 JS 文件:
<script src="node_modules/@dojo/themes/dojo/dojo-{version}.js"></script>
一旦在項目中安裝了 @dojo/themes
,就可將其作為擴展應用程序主題的基礎,在新的主題中使用 CSS 模塊化的組合功能來包含相關的組件。
@dojo/themes
中也包含一個 擁有 :root
的 variables.css
文件,如果擴展的應用程序主題需要在新主題的某處引用 Dojo 內置的屬性,則可以導入該文件。
下面是一個 @dojo/widgets/button
使用新主題的示例,擴展自 @dojo/themes
,將按鈕的背景色改為綠色,而其他的主題樣式屬性保持不變:
src/themes/myTheme/theme.ts
import * as myButton from './myButton.m.css';
export default {
'@dojo/widgets/button': myButton
};
src/themes/myTheme/myButton.m.css
@import '@dojo/themes/dojo/variables.css';
.root {
composes: root from '@dojo/themes/dojo/button.m.css';
background-color: var(--dojo-green);
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。