深入研究了Web版Photoshop,发现了这些有趣又有用的CSS知识!

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

两周前,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: 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 {
  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 的例子。

导航栏

我很喜欢这部分的元素类命名,它没有使用 left、center、right,而是使用了start、center、end。

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

操作

在构建像 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,发现它们全部都是 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来避免变形。

责任编辑:姜华 来源: 前端充电宝
相关推荐

2022-06-16 08:35:10

CSS属性前端

2022-05-11 09:03:05

CSS容器文本换行

2020-05-18 08:42:23

CSS背景图像前端开发

2024-05-20 08:25:55

2023-02-26 01:02:22

2019-09-25 09:00:56

iPhone 11拆解苹果

2020-08-04 08:48:34

数据弹屏技术

2012-10-18 09:34:57

CSSdisplayDIV

2012-10-29 10:30:36

CSSWeb前端display

2022-01-11 09:01:14

Python可视化Python基础

2018-08-28 12:07:42

微信数据分析

2018-03-07 09:35:08

Python淘宝数据

2011-12-15 10:43:20

JavaNIO

2021-05-07 09:03:27

算法模型技术

2024-11-05 09:47:08

VGG网络模型

2020-06-30 10:38:36

Python 开发编程语言

2023-06-24 23:11:07

2021-10-15 06:22:07

勒索软件攻击数据泄露

2020-11-05 11:10:43

程序员开发工具

2021-06-08 13:55:41

AI 数据人工智能
点赞
收藏

51CTO技术栈公众号