研究了Web版Photoshop,提升自己=提升价值

Adobe 发布了Web版 Photoshop🔗,它是使用 WebAssembly、web components、P3 颜色等 Web 技术构建的。本文就来研究一下网页版 Photoshop 上有趣又有用的 CSS 知识!

图片

Photoshop 旧 Logo

首先,在浏览器控制台中使用了 Photoshop 的 Logo(1990-1991)。

图片

这是如何实现的呢?这里是代码:

 
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这样的应用在Web上感觉像一个真正的应用,第一件事是防止滚动。为了实现这一点,<body>元素具有position: fixedoverflow: 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 {
  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 布局

在构建现代的 Web 应用时,使用 Flex 布局具有很多好处。网页版 Photoshop 就用到了很多 Flex 布局。

图片

使用 Flexbox 使构建组件变得更容易。下面来看几个使用 Flexbox 的例子。

导航栏

我很喜欢这部分的元素类命名,它没有使用 leftcenterright,而是使用了startcenterend

图片

在应用可以从左到右(LTR)或从右到左(RTL)工作时,这种逻辑命名是正确的做法。

操作

在构建像 Photoshop 这样的复杂应用时,嵌套的 Flexbox 容器是很常见的。下图显示了操作栏中的两个容器。

图片

第一个容器用于缩放。第二个容器包含所有的操作和按钮。

 
.container {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--spectrum-global-dimension-size-50);
}
  • 使用gap属性非常有助于定义间距,使用marginpadding来实现这个效果可能会变得混乱。

  • .container这个类名太过通用了,但在这里非常适用,因为这是一个Web组件,所以所有的样式都被封装起来了。

图层

图层功能是Photoshop的重要组成部分,仔细观察CSS,发现它们全部都是 Flexbox 布局。

图片

下面是图层组件的 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()表示图层组件。

  • 这感觉像是条件式 CSS。如果HTML属性indent=1存在,那么就更改第一列的padding-right

 
:host([dir="ltr"][indent="1"]) #first-column {
  padding-right: var(--spectrum-global-dimension-size-200);
}

如果缩进是两个级别,那么可以通过CSS的calc()函数,将padding-right的值乘以 2。

 
:host([dir="ltr"][indent="2"]) #first-column {
  padding-right: calc(2 * var(--spectrum-global-dimension-size-200));
}

在浏览器中,尝试嵌套到第 6 层:

图片

而知名的设计软件 Figma 是使用间隔组件来增加嵌套层的间距的。

图片

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 行的主网格。其中,第一行的高度根据内容自适应调整行高,而第二行则占据了所有剩余可用空间。

 
:host {
  display: grid;
  grid-template-columns: 100%;
}

:host(:not([direction^="vertical"])) {
  grid-template-rows: auto 1fr;
}

这里有几个要点:

  • 使用CSS的:not()选择器。

  • 使用[attr^=value]选择器来排除具有以vertical开头的值的direction属性的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);
}

使用 Firfox 开发者工具来调试这个网格,它会生成模拟的网格布局。当突出显示矩形时,它将显示放置在实际的网格中。

图片

这里使用的技术就是命名网格线,其思想是给每个列或行指定一个名称,然后定义其宽度。列和行的宽度可以是automin-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。锁定按钮位于网格的中心,但它需要略微从左边和顶部位置进行插入。

 
.lock-button {
  grid-area: horizontal / size-locks / horizontal / size-locks;
  position: absolute;
  left: 8px;
  top: 22px;
}

输入

下面来看看使用 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 网格有点大材小用了。

 
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 网格是针对这个小组件的,感觉是有点多此一举了。

 
.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变量的广泛使用

更改图层缩略图的大小

Photoshop 可以控制缩略图大小。当有很多图层并且想要在更小的空间中查看更多图层时,这非常有用。

图片

Adobe 团队的构建方式很有趣。首先,图层面板的主容器上有一个 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);
}

加载进度

通过使用size属性来管理组件的大小。CSS 变量会根据不同的 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 会更简单(没有边框)。

图片

这也是通过 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
  );
}

单选按钮

在这个例子中,使用 CSS 变量来根据 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
  );
}

菜单处于活动状态时锁定页面

当主菜单处于活动状态时,会有一个名为 holder 的元素填充整个屏幕(遮罩层),并位于菜单下方。

图片

 
#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 视口单位。混合模式菜单的最大高度为 55vh(视口高度的55%)。

图片

 
sp-menu {
  max-height: 55vh;
  --mod-menu-item-min-height: auto;
}

::slotted(*) {
  overscroll-behavior: contain;
}

这里还使用了 overscroll-behavior: contains,这是避免滚动正文内容的一个很棒的功能。

图片

图层缩略图

在图层面板中,缩略图使用了object-fit: contain来避免变形。

图片

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑着牛的奇兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值