深入研究了Web版Photoshop,發(fā)現(xiàn)了這些有趣又有用的CSS知識!
兩周前,Adobe 發(fā)布了Web版 Photoshop??,它是使用 WebAssembly、web components、P3 顏色等 Web 技術(shù)構(gòu)建的。本文就來研究一下網(wǎng)頁版 Photoshop 上有趣又有用的 CSS 知識!
Photoshop 舊 Logo
首先,在瀏覽器控制臺(tái)中使用了 Photoshop 的 Logo(1990-1991)。
這是如何實(shí)現(xiàn)的呢?這里是代碼:
console.info(
"%c %cAdobe %cPhotoshop Web%c %c2023.20.0.0%c %c1bba617e276",
"padding-left: 36px; line-height: 36px; background-image: url('data:image/gif;base64,R0lGODlhIAAgAPEBAAAAAPw==');"
)
Body 元素
要讓像Photoshop這樣的應(yīng)用在Web上感覺像一個(gè)真正的應(yīng)用,第一件事是防止滾動(dòng)。為了實(shí)現(xiàn)這一點(diǎn),<body>元素具有position: fixed和overflow: hidden。
body,
html {
height: 100%;
}
body {
font-family: adobe-clean, sans-serif;
margin: 0;
overflow: hidden;
position: fixed;
width: 100%;
}
在<body>元素內(nèi)部,也有多個(gè)根元素。
<psw-app>
<psw-app-context>
<ue-video-surface>
<ue-drawer>
<div id="appView">
<psw-app-navbar></psw-app-navbar>
<psw-document-page></psw-document-page>
</div>
</ue-drawer>
</ue-video-surface>
</psw-app-context>
</psw-app>
最內(nèi)部有一個(gè)包含導(dǎo)航和文檔頁面的元素。
#appView {
background-color: var(--editor-background-color);
color: var(--spectrum-global-color-gray-800);
display: flex;
flex-direction: column;
}
* {
touch-action: manipulation;
}
:host {
position: relative;
}
Flexbox 布局
在構(gòu)建現(xiàn)代的 Web 應(yīng)用時(shí),使用 Flex 布局具有很多好處。網(wǎng)頁版 Photoshop 就用到了很多 Flex 布局。
使用 Flexbox 使構(gòu)建組件變得更容易。下面來看幾個(gè)使用 Flexbox 的例子。
導(dǎo)航欄
我很喜歡這部分的元素類命名,它沒有使用 left、center、right,而是使用了start、center、end。
在應(yīng)用可以從左到右(LTR)或從右到左(RTL)工作時(shí),這種邏輯命名是正確的做法。
操作
在構(gòu)建像 Photoshop 這樣的復(fù)雜應(yīng)用時(shí),嵌套的 Flexbox 容器是很常見的。下圖顯示了操作欄中的兩個(gè)容器。
第一個(gè)容器用于縮放。第二個(gè)容器包含所有的操作和按鈕。
.container {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: var(--spectrum-global-dimension-size-50);
}
- 使用gap屬性非常有助于定義間距,使用margin或padding來實(shí)現(xiàn)這個(gè)效果可能會(huì)變得混亂。
- .container這個(gè)類名太過通用了,但在這里非常適用,因?yàn)檫@是一個(gè)Web組件,所以所有的樣式都被封裝起來了。
圖層
圖層功能是Photoshop的重要組成部分,仔細(xì)觀察CSS,發(fā)現(xiàn)它們?nèi)慷际?Flexbox 布局。
下面是圖層組件的 HTML 結(jié)構(gòu):
<psw-tree-view-item indent="0" layer-visible can-open dir="ltr" open>
<div id="link">
<span id="first-column"></span>
<span id="second-column"></span>
<span id="label"></span>
</div>
</psw-tree-view-item>
這里使用 ID 是完全沒問題的,因?yàn)檫@是一個(gè) Web 組件,因此 #first-column ID 在頁面上出現(xiàn)多少次并不重要。
#link元素是一個(gè) Flexbox 容器,#label元素也是一個(gè) Flexbox 容器。
<div class="layer-content layer-wrapper selected">
<psw-layer-thumbnail></psw-layer-thumbnail>
<div class="name" title="Layer name">Layer name</div>
<div class="actions"></div>
<overlay-trigger></overlay-trigger>
</div>
下面來舉一個(gè)例子說明如何完成子層的縮進(jìn)。
- :host()表示圖層組件。
- 這感覺像是條件式 CSS。如果HTML屬性indent=1存在,那么就更改第一列的padding-right。
:host([dir="ltr"][indent="1"]) #first-column {
padding-right: var(--spectrum-global-dimension-size-200);
}
如果縮進(jìn)是兩個(gè)級別,那么可以通過CSS的calc()函數(shù),將padding-right的值乘以 2。
:host([dir="ltr"][indent="2"]) #first-column {
padding-right: calc(2 * var(--spectrum-global-dimension-size-200));
}
在瀏覽器中,嘗試嵌套到第 6 層:
而知名的設(shè)計(jì)軟件 Figma 是使用間隔組件來增加嵌套層的間距的。
Grid 布局
新文件模式
在創(chuàng)建新的 Photoshop 文件時(shí),可以選擇預(yù)定義的尺寸列表。為了實(shí)現(xiàn)這一點(diǎn),有一個(gè)包含多個(gè)選項(xiàng)卡和一個(gè)活動(dòng)面板的布局。
HTML 如下所示:
<sp-tabs
id="tabs"
quiet=""
selected="2"
size="m"
direction="horizontal"
dir="ltr"
focusable=""
>
<div id="list"></div>
<slot name="tab-panel"></slot>
</sp-tabs>
在 CSS 中,有一個(gè) 1 列 2 行的主網(wǎng)格。其中,第一行的高度根據(jù)內(nèi)容自適應(yīng)調(diào)整行高,而第二行則占據(jù)了所有剩余可用空間。
:host {
display: grid;
grid-template-columns: 100%;
}
:host(:not([direction^="vertical"])) {
grid-template-rows: auto 1fr;
}
這里有幾個(gè)要點(diǎn):
- 使用CSS的:not()選擇器。
- 使用[attr^=value]選擇器來排除具有以vertical開頭的值的direction屬性的HTML元素。
這些都是條件式 CSS 技術(shù)。
嘗試將direction屬性更改為vertical,結(jié)果符合預(yù)期。
下面是基于屬性變化的CSS:
:host([direction^="vertical"]) {
grid-template-columns: auto 1fr;
}
:host([direction^="vertical-right"]) #list #selection-indicator,
:host([direction^="vertical"]) #list #selection-indicator {
inline-size: var(
--mod-tabs-divider-size,
var(--spectrum-tabs-divider-size)
);
inset-block-start: 0px;
inset-inline-start: 0px;
position: absolute;
}
為了突出顯示哪個(gè)選項(xiàng)卡是活動(dòng)狀態(tài),有一個(gè)相對于選項(xiàng)卡列表定位的#selection-indicator元素。
圖層屬性
這部分的 CSS 網(wǎng)格用法很有趣,它適合解決對齊網(wǎng)格中許多元素的問題。
深入研究 CSS:
.content {
position: relative;
display: grid;
grid-template-rows: [horizontal] min-content [vertical] min-content [transforms] min-content [end];
grid-template-columns: [size-labels] min-content [size-inputs] auto [size-locks] min-content [space] min-content [position-labels] min-content [position-inputs] auto [end];
row-gap: var(--spectrum-global-dimension-size-150);
}
使用 Firfox 開發(fā)者工具來調(diào)試這個(gè)網(wǎng)格,它會(huì)生成模擬的網(wǎng)格布局。當(dāng)突出顯示矩形時(shí),它將顯示放置在實(shí)際的網(wǎng)格中。
這里使用的技術(shù)就是命名網(wǎng)格線,其思想是給每個(gè)列或行指定一個(gè)名稱,然后定義其寬度。列和行的寬度可以是auto
或min-content
,這是創(chuàng)建動(dòng)態(tài)網(wǎng)格的一種很好的方法。
這樣,每個(gè)網(wǎng)格項(xiàng)都可以在網(wǎng)格內(nèi)部進(jìn)行定位:
.horizontal-size-label {
grid-area: horizontal / size-labels / horizontal / size-labels;
}
.vertical-position-input {
grid-area: vertical / position-inputs / vertical / position-inputs;
}
.horizontal-position-input {
grid-area: horizontal / position-inputs / horizontal /
position-inputs;
}
還有一個(gè)細(xì)節(jié)是對網(wǎng)格項(xiàng)使用了position: absolute。鎖定按鈕位于網(wǎng)格的中心,但它需要略微從左邊和頂部位置進(jìn)行插入。
.lock-button {
grid-area: horizontal / size-locks / horizontal / size-locks;
position: absolute;
left: 8px;
top: 22px;
}
輸入
下面來看看使用 CSS 網(wǎng)格來布局輸入字段的用例。
:host([editable]) {
display: grid;
grid-template-areas:
"label ."
"slider number";
grid-template-columns: 1fr auto;
}
:host([editable]) #label-container {
grid-area: label / label / label / label;
}
:host([editable]) #label-container + div {
grid-area: slider / slider / slider / slider;
}
:host([editable]) sp-number-field {
grid-area: number / number / number / number;
}
在瀏覽器中檢查時(shí),可以看到網(wǎng)格線名稱或網(wǎng)格區(qū)域名稱。
- 網(wǎng)格區(qū)域名稱
- 網(wǎng)格線名稱
菜單項(xiàng)
在我看來,在這里使用 CSS 網(wǎng)格有點(diǎn)大材小用了。
sp-menu-item {
display: grid;
grid-template-areas:
". chevronAreaCollapsible . iconArea sectionHeadingArea . . ."
"selectedArea chevronAreaCollapsible checkmarkArea iconArea labelArea valueArea actionsArea chevronAreaDrillIn"
". . . . descriptionArea . . ."
". . . . submenuArea . . .";
grid-template-columns: auto auto auto auto 1fr auto auto auto;
grid-template-rows: 1fr auto auto auto;
}
這是一個(gè)包含 8列 4行的網(wǎng)格。這里似乎一次只能激活一個(gè)網(wǎng)格行,其他行將由于內(nèi)容為空或HTML元素的缺失而折疊。
有趣的是,上面的 CSS 是簡化過的版本。原始版本看起來像這樣,使用了grid-template縮寫。
下面是在應(yīng)用中找到的一些菜單項(xiàng):
CSS 網(wǎng)格是針對這個(gè)小組件的,感覺是有點(diǎn)多此一舉了。
.checkmark {
align-self: start;
grid-area: checkmarkArea / checkmarkArea / checkmarkArea /
checkmarkArea;
}
#label {
grid-area: labelArea / labelArea / labelArea / labelArea;
}
::slotted([slot="value"]) {
grid-area: valueArea / valueArea / valueArea / valueArea;
}
注意,CSS 網(wǎng)格的暗部分處于非活動(dòng)狀態(tài)。它們折疊了,因?yàn)闆]有內(nèi)容。對于這個(gè)例子,也可以這樣來實(shí)現(xiàn):
.checkmark {
align-self: start;
grid-area: checkmarkArea;
}
#label {
grid-area: labelArea;
}
::slotted([slot="value"]) {
grid-area: valueArea;
}
當(dāng)每列和行的值相同時(shí),無需定義它們的開始和結(jié)束位置。
CSS變量的廣泛使用
更改圖層縮略圖的大小
Photoshop 可以控制縮略圖大小。當(dāng)有很多圖層并且想要在更小的空間中查看更多圖層時(shí),這非常有用。
Adobe 團(tuán)隊(duì)的構(gòu)建方式很有趣。首先,圖層面板的主容器上有一個(gè) HTML 屬性large-thumbs。
<psw-layers-panel large-thumbs></psw-layers-panel>
在CSS中,有一個(gè):host([large-thumbs])選擇器,它分配了特定的CSS變量。
:host([large-thumbs]) {
--psw-custom-layer-thumbnail-size: var(
--spectrum-global-dimension-size-800
);
--psw-custom-layer-thumbnail-border-size: var(
--spectrum-global-dimension-size-50
);
}
對于每個(gè)圖層,都有一個(gè)名為 psw-layer-thumbnail 的元素,這是將應(yīng)用CSS變量的地方。
<psw-layers-panel-item>
<psw-tree-view-item>
<psw-layer-thumbnail class="thumb"></psw-layer-thumbnail>
</psw-tree-view-item>
</psw-layers-panel-item>
這里,CSS 變量被分配給縮略圖。
:host {
--layer-thumbnail-size: var(
--psw-custom-layer-thumbnail-size,
var(--spectrum-global-dimension-size-400)
);
--layer-badge-size: var(--spectrum-global-dimension-size-200);
position: relative;
width: var(--layer-thumbnail-size);
min-width: var(--layer-thumbnail-size);
height: var(--layer-thumbnail-size);
}
加載進(jìn)度
通過使用size屬性來管理組件的大小。CSS 變量會(huì)根據(jù)不同的 size 而變化。
:host([size="m"]) {
--spectrum-progressbar-size-default: var(
--spectrum-progressbar-size-2400
);
--spectrum-progressbar-font-size: var(--spectrum-font-size-75);
--spectrum-progressbar-thickness: var(
--spectrum-progress-bar-thickness-large
);
--spectrum-progressbar-spacing-top-to-text: var(
--spectrum-component-top-to-text-75
);
}
圖像控制
如果 HTML 存在屬性quiet,則 UI 會(huì)更簡單(沒有邊框)。
這也是通過 CSS 變量完成的。
:host([quiet]) {
--spectrum-actionbutton-background-color-default: var(
--system-spectrum-actionbutton-quiet-background-color-default
);
--spectrum-actionbutton-background-color-hover: var(
--system-spectrum-actionbutton-quiet-background-color-hover
);
}
單選按鈕
在這個(gè)例子中,使用 CSS 變量來根據(jù) HTML 的 size 屬性值來改變單選按鈕的大小。
<sp-radio size="m" checked="" role="radio"></sp-radio>
:host([size="m"]) {
--spectrum-radio-height: var(--spectrum-component-height-100);
--spectrum-radio-button-control-size: var(
--spectrum-radio-button-control-size-medium
);
}
菜單處于活動(dòng)狀態(tài)時(shí)鎖定頁面
當(dāng)主菜單處于活動(dòng)狀態(tài)時(shí),會(huì)有一個(gè)名為 holder 的元素填充整個(gè)屏幕(遮罩層),并位于菜單下方。
#actual[aria-hidden] + #holder {
display: flex;
}
#holder {
display: none;
align-items: center;
justify-content: center;
flex-flow: column;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
此元素是為了防止用戶單擊或?qū)⑹髽?biāo)懸停在頁面的其他部分,以確保用戶只能對菜單進(jìn)行操作,這可能是為了模仿桌面應(yīng)用而設(shè)置的。
混合模式菜單
這里使用了 CSS 視口單位?;旌夏J讲藛蔚淖畲蟾叨葹?nbsp;55vh(視口高度的55%)。
sp-menu {
max-height: 55vh;
--mod-menu-item-min-height: auto;
}
::slotted(*) {
overscroll-behavior: contain;
}
這里還使用了 overscroll-behavior: contains,這是避免滾動(dòng)正文內(nèi)容的一個(gè)很棒的功能。
圖層縮略圖
在圖層面板中,縮略圖使用了object-fit: contain來避免變形。