本文翻译自 CSS Findings From Photoshop Web Version ,作者:Ahmad, 略有删改。
几周前,Adobe发布了一个Web版的Photoshop,它是用WebAssembly、Web组件、P3颜色等网络技术构建的。
Photoshop是我14岁时学会的第一个专业设计应用程序。这是我成为设计师并最终成为前端开发人员的原因之一。正因为如此,我认为看看CSS是如何助力像Photoshop这样的大型应用开发会很有趣。
在这篇文章中,我将分享在Web版的Photoshop中我觉得有趣的CSS发现。
Photoshop 旧版Logo
我注意到的第一件事是在浏览器控制台中使用Photoshop(1990-1991)的旧Logo。
你会对这样的东西是如何制作的感到好奇吗?以下是代码:
console.info(
"%c %cAdobe %cPhotoshop Web%c %c2023.22.0.0%c %c56043548b47",
"padding-left: 36px; line-height: 36px; background-image: url('data:image/gif;base64,R0lGODlhIAAgAPEBAAAAAP///wAAAAAAACH5BAEAAAIALAAAAAAgACAAAAKkhC+py3zfopxGvIsztsCGD4La2FVAGBoBuZnox45WcqLsum5KDWdvf1nwTo+fLbgDqo7LFCJJbOY0oidt6ozVKrtib0qaCnlYcJh7rf5iK6HZaM64VeS6L+pWf89WT+6vRAUBBVQ1gpOTJ4IYdxCnOBSJ8ZhkZNekk5ZSxpTpt6Y1eEVm00j3VALDmBXVyPEJB2r2ShoLh2ASqvU60dsr5yuBUQAAOw=='); background-size: 32px; background-repeat: no-repeat; background-position: 2px 2px", "background: #666; border-radius:0.5em 0 0 0.5em; padding:0.2em 0em 0.1em 0.5em; color: white; font-weight: bold", "background: #666; border-radius:0 0.5em 0.5em 0; padding:0.2em 0.5em 0.1em 0em; color: white;", "", "background: #c3a650; border-radius:0.5em; padding:0.2em 0.5em 0.1em 0.5em; color: white;", "", "background: #15889f; border-radius:0.5em; padding:0.2em 0.5em 0.1em 0.5em; color: white;");
body 元素
要让Photoshop这样的应用在Web网页上有真实应用的感觉,首先需要防止滚动。为了实现这一点,<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>
元素内部,也有多个根元素。
<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>
最里面是包含导航和文档页面的元素 #appView
。
#appView {
background-color: var(--editor-background-color);
color: var(--spectrum-global-color-gray-800);
display: flex;
flex-direction: column;
}
几乎都是 Flexbox 布局
当构建一个web应用程序时,使用flexbox
有很多好处。当我想到Flexbox
和Photoshop一起出现时,我的感觉是很复杂。
Photoshop是一个著名的设计软件,是许多人进入设计领域的第一款软件。另一方面使用Flexbox构建组件变得更容易,对CSS对新手来说更容易。
无需使用clearfix
清除浮动,只需添加display: flex
,然后根据需要设置子项的样式。让我们探索Photoshop中的相关Flexbox
使用情况。
导航栏
我喜欢这里的命名。他们使用“start
”,“center
”和“end
”,而不是使用“left
,center
,right
”,。
对于可以从左到右(LTR)或从右到左(RTL)工作的应用程序来说,这种逻辑命名是正确的。
Context Bar
在构建像Photoshop这样的复杂应用程序时,嵌套的flexbox
容器是必要的。在下图中,我在上下文栏中突出显示了两个容器。
第一个容器用于抓取动作。第二个容器包含所有操作和按钮。
.container {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: var(--spectrum-global-dimension-size-50);
}
-
gap
的使用对定义间距有很大帮助。相比使用margin或padding就好多了。 -
名称
.container
太通用了,但它在这里恰到好处,因为这是一个Web组件,所有的样式都被封装在内部。
图层
由于图层功能是Photoshop的重要组成部分,因此它可能是新手将要学习的前几件事之一。我好奇地检查了它们背后的CSS实现。
这里是层组件的HTML代码:
<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是完全可以的吗?由于这是一个Web组件,所以#first-column ID
在页面上出现多少次并不重要。
#link
元素是主要的flexbox
包装器,#label
中的元素也是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>
让我们看看子层的缩进是如何完成的。
:host()
表示层组件- 如果有HTML属性存在
indent=1
,则更改第一列的padding-right
。
CSS
:host
是一个伪类选择器,它用于选择当前组件的宿主元素。:host
选择器只能在 Shadow DOM 中使用,因为它选择的是组件的根元素,而不是组件内部的子元素。
:host([dir="ltr"][indent="1"]) #first-column {
padding-right: var(--spectrum-global-dimension-size-200);
}
如果是indent=2
,则通过CSS calc()
函数将padding-right
的值乘以2。
:host([dir="ltr"][indent="2"]) #first-column {
padding-right: calc(2 * var(--spectrum-global-dimension-size-200));
}
在浏览器中,我尝试嵌套到第6级。下面是一张真实的截图:
当看到这个的时候,我检查Figma
背后的CSS实现。他们使用了一个间隔组件来增加嵌套层的间距。
有趣的是,两个主要的设计应用程序使用了不同的技术来实现相同的目标。
关于 CSS Grid 布局
新建文件弹窗
创建新的Photoshop文件时,您可以选择预定义的大小列表。为了实现这一点,有一个包含多个选项卡和一个活动面板的布局。
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中,有一个1列2行的主网格。第一行是auto,第二行跨越可用空间。
:host {
display: grid;
grid-template-columns: 100%;
}
:host(:not([direction^="vertical"])) {
grid-template-rows: auto 1fr;
}
这里有几件事:
- 使用CSS
:not()
选择器 - 使用
[attr^=value]
选择器排除属性direction
的值以vertical
开头的HTML元素。
我认为这是一种条件CSS技术。
我尝试将direction属性更改为vertical。
下面是基于属性更改的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;
}
要突出显示哪个选项卡项处于活动状态,有一个相对于选项卡列表定位的#selection-indicator
元素。
图层属性
我很喜欢这里的CSS网格。它适用于在网格中对齐多个元素的问题。
在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);
}
这里使用的技术称为命名网格线。这个想法是你命名每个列或网格,然后定义其宽度。列和行的宽度为auto
或min-content
。这是制作动态网格的好方法。
这样每个网格项都应该定位在网格中。以下是一些例子:
.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;
}
另一个引起我注意的细节是在网格项中使用position: absolute
。锁定按钮被放置在网格的中心,但它需要在left
和top
位置稍微偏移一些。
.lock-button {
grid-area: horizontal / size-locks / horizontal / size-locks;
position: absolute;
left: 8px;
top: 22px;
}
Drop-Shadow 输入框
这是许多CSS网格用于输入字段布局的示例。
: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;
}
在浏览器中检查时,可以看到轴网线名称或轴网区域名称。
对应网格线名称:
你可以用两种不同的方式查看布局,对于调试或理解您试图构建/修复的布局非常有用。 CSS网格应该在我们的Web应用程序中更多地使用,但绝对不像下面的例子。
菜单网格
我认为在这里使用CSS网格布局有点过头了,下面说明一下我的理解。
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;
}
这是一个包含8列 * 4行的网格。从我花费的时间来理解他们为什么这样做,似乎一次只有一行网格是活跃的,其他行会因为内容为空或者缺少HTML元素而折叠。
有趣的是,上面的CSS是我简化后的。原始版本看起来像这样,团队使用了grid-template
速记。
以下是我可以在应用程序中找到的相关菜单项。
这个 CSS 网格是为了这个小组件而设计的,我认为在这里使用 CSS 网格是一种过度设计。
下面是一个使用网格的例子。
.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 网格中的灰色部分是不活动的。它们因为没有内容而被折叠了。对于这个具体的例子,作者也可以这样做:
.checkmark {
align-self: start;
grid-area: checkmarkArea;
}
#label {
grid-area: labelArea;
}
::slotted([slot="value"]) {
grid-area: valueArea;
}
当它们是相同的值时,不需要定义每个列和行的开始和结束。
大量使用CSS变量
我真的很喜欢CSS变量如何用来改变UI。我将着重指出这方面的多个例子。
更改图层缩略图的大小
如果您熟悉Photoshop,则可以控制缩略图大小并使其更小。当您有很多层,并希望在更少的空间中查看更多层时,这很有用。
首先层面板的主容器上有一个HTML属性large-thumbs
。
<psw-layers-panel large-thumbs></psw-layers-panel>
在CSS中,有: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
);
}
对于每个层,都有一个名为psw-layer-thumbnail
的元素。这是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);
}
Loading 进度条
管理组件的大小是通过使用属性size
来完成的,CSS变量根据大小而变化。
: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属性quite
存在,则UI更简单。
这也可以通过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
);
/* And a lot more styles that I removed for the purpose of keeping the article clean. */
}
单选按钮
在这个例子中,团队使用CSS变量根据size
HTML属性更改单选按钮的大小。
<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
);
/* And a lot more styles that I removed for the purpose of keeping the article clean. */
}
当菜单处于活动状态时锁定页面
当主菜单处于活动状态时,有一个“保持器”元素填充整个屏幕,位于菜单下方。
#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;
}
此元素用于防止用户点击或悬停在页面的其他部分,看起来像在模仿桌面应用程序。
混合模式菜单
我在这里发现了CSS viewport
单元的用途。混合模式菜单的最大高度为55vh
。
sp-menu {
max-height: 55vh;
--mod-menu-item-min-height: auto;
}
::slotted(*) {
overscroll-behavior: contain;
}
overscroll-behavior: contain
也有用到。这是一个很好的功能,可以避免滚动正文内容。
注释组件
用户可以在画布上的任何地方钉上注释或绘图。我检查了组件,以了解它是如何构建的。
我喜欢动态定位和颜色的CSS变量
为了将每个评论放置在用户选择的位置,团队使用了通过JS提供的CSS变量来处理。
<div
data-html2canvas-ignore="true"
class="Pin__component ccx-annotation"
style="
--offset-x: 570.359375px;
--offset-y: 74.23046875px;
--ccx-comments-pin-color: #16878C;
"
></div>
.Pin__component {
--pin-diameter: 24px;
left: calc(var(--offset-x) - var(--pin-diameter) / 2);
top: calc(var(--offset-y) - var(--pin-diameter) / 2);
position: absolute;
height: var(--pin-diameter);
width: var(--pin-diameter);
border-radius: var(--pin-diameter);
border: 1px solid white;
background: var(--ccx-comments-pin-color);
}
使用SVG进行工程图标注
当你将图片缩小时,SVG笔划不会调整大小,而且看起来很粗。
据我所知,这可以通过添加vector-effect: non-scaling-stroke
来解决。但我没试过。
对图层缩略图使用 Object-Fit: Contain
在图层面板中,缩略图使用object-fit: contain
以避免失真。
最后
文章到此就结束了,介绍了Photoshop Web版本使用的一些CSS技术。与国内常见的CSS技术相比,有许多不同之处,其中很多部分值得学习和借鉴。当然这只是其中的一部分,如果你感兴趣,可以查看他们的源代码来深入研究。
看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~
专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)