This article is translated from CSS Findings From Photoshop Web Version
, by Ahmad, slightly edited.
A few weeks ago, Adobe released a web version of Photoshop built with web technologies such as WebAssembly, Web Components, and P3 Color.
Photoshop was the first professional design application I learned when I was 14 years old. This is one of the reasons I became a designer and eventually a front-end developer. Because of this, I thought it would be interesting to see how CSS aids the development of large-scale applications like Photoshop.
In this article, I’ll share some interesting CSS discoveries I’ve made in the web version of Photoshop.
Photoshop old version Logo
The first thing I noticed was the old logo from Photoshop (1990-1991) in the browser console.
Are you curious about how something like this is made? Here is the code:
console.info( "%c ?dobe %cPhotoshop Web%c ?023.22.0.0%c ?6043548b47", "padding-left: 36px; line-height: 36px; background-image: url('data:image/gif;base64,R0lGODlhIAAgAPEBAAAAAP///wAAAAAAACH5BAEAAAIALAAAAAAgACAAAAKkhC + py3zfopxGvIsztsCGD4La2FVAGBoBuZnox45WcqLsum5KDWdvf1n wTo + fLbgDqo7LFCJJbOY0oidt6ozVKrtib0qaCnlYcJh7rf5iK6HZaM64VeS6L + pWf89WT + 6vRAUBBVQ1gpOTJ4IYdxCnOBSJ8ZhkZNekk5ZSxpTpt6Y1eEVm00j3VALDmBXVyPEJB2r2S hoLh2ASqvU60dsr5yuBUQAAOw=='); 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 element
To make an application like Photoshop feel like a real application on a Web page, it first needs to prevent scrolling. To achieve this, the element is set with
position: fixed
and overflow: hidden
.
body, html {<!-- --> height: 100%; } body {<!-- --> font-family: adobe-clean, sans-serif; margin: 0; overflow: hidden; position: fixed; width: 100%; }
Inside the element, there are also multiple root elements.
<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>
The innermost element is #appView
which contains the navigation and documentation pages.
#appView {<!-- --> background-color: var(--editor-background-color); color: var(--spectrum-global-color-gray-800); display: flex; flex-direction: column; }
Almost all Flexbox layouts
There are many benefits to using flexbox
when building a web application. When I think about Flexbox
appearing together with Photoshop, I have mixed feelings.
Photoshop is a well-known design software and is the first software for many people to enter the design field. Building components with Flexbox on the other hand becomes easier, and CSS is easier for newbies.
Instead of clearing the float with clearfix
, just add display: flex
and style the children as needed. Let’s explore related Flexbox
usage in Photoshop.
Navigation bar
I like the naming here. They use “start
“, “center
” and “end
” instead of using “left
, center
, right
”,.
This logical naming is correct for applications that can work from left to right (LTR) or right to left (RTL).
Context Bar
Nested flexbox
containers are necessary when building complex applications like Photoshop. In the image below, I have highlighted two containers in the context bar.
The first container is used for grab actions. The second container contains all actions and buttons.
.container {<!-- --> display: flex; flex-wrap: nowrap; align-items: center; gap: var(--spectrum-global-dimension-size-50); }
-
The use of
gap
is very helpful in defining spacing. It is much better than using margin or padding. -
The name
.container
is too generic, but it’s just right here since this is a web component and all the styling is encapsulated inside.
Layer
Since the layer feature is an important part of Photoshop, it is probably one of the first few things a newbie will learn. I curiously checked the CSS implementation behind them.
Here is the HTML code for the layer component:
<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>
Do you think it’s totally okay to use ID here? Since this is a web component, it doesn’t matter how many times #first-column ID
appears on the page.
The #link
element is the main flexbox
wrapper, and the elements within #label
are also flexbox
wrappers.
<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>
Let’s see how indentation of sublayers is done.
:host()
Presentation layer component- If there is an HTML attribute
indent=1
, change thepadding-right
of the first column.
CSS
:host
is a pseudo-class selector that is used to select the host element of the current component. The:host
selector can only be used in Shadow DOM because it selects the root element of the component, not the child elements inside the component.
:host([dir="ltr"][indent="1"]) #first-column {<!-- --> padding-right: var(--spectrum-global-dimension-size-200); }
If it is indent=2
, the value of padding-right
is multiplied by 2 through the CSS calc()
function.
:host([dir="ltr"][indent="2"]) #first-column {<!-- --> padding-right: calc(2 * var(--spectrum-global-dimension-size-200)); }
In the browser I tried nesting to level 6. Here is a real screenshot:
When seeing this, I checked the CSS implementation behind Figma
. They used a spacer component to increase the spacing between nested layers.
Interestingly, the two major design applications use different techniques to achieve the same goal.
About CSS Grid layout
New file pop-up window
When creating a new Photoshop file, you can choose from a predefined list of sizes. To achieve this, there is a layout with multiple tabs and an active panel.
The HTML code is as follows:
<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>
In CSS, there is a main grid with 1 column and 2 rows. The first line is auto, the second line spans the available space.
:host {<!-- --> display: grid; grid-template-columns: 100%; } :host(:not([direction^="vertical"])) {<!-- --> grid-template-rows: auto 1fr; }
A few things here:
- Use CSS
:not()
selector - Use the
[attr^=value]
selector to exclude HTML elements whose value starts withvertical
.
I think this is a conditional CSS technique.
I tried changing the direction attribute to vertical.
Here is the CSS based on the property changes:
: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; }
To highlight which tab item is active, there is a #selection-indicator
element positioned relative to the tab list.
Layer properties
I really like the CSS grid here. It works well for the problem of aligning multiple elements in a grid.
In the CSS I noticed the following code:
.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); }
The technique used here is called named grid lines. The idea is that you name each column or grid and then define its width. Column and row widths are auto
or min-content
. This is a great way to make dynamic meshes.
This way every grid item should be positioned within the grid. Here are some examples:
.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; }
Another detail that caught my attention is the use of position: absolute
in grid items. The lock button is placed in the center of the grid, but it needs to be slightly offset at the left
and top
positions.
.lock-button {<!-- --> grid-area: horizontal / size-locks / horizontal / size-locks; position: absolute; left: 8px; top: 22px; }
Drop-Shadow input box
Here is an example of many CSS grids being used for input field layout.
: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; }
When inspecting in a browser, you can see grid line names or grid area names.
Corresponding grid line name:
You can view the layout in two different ways, useful for debugging or understanding the layout you are trying to build/fix.
CSS grid should be used more often in our web applications, but definitely not like the example below.
Menu grid
I think using CSS grid layout is a bit over the top here, so here’s my understanding.
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; }
This is a grid with 8 columns * 4 rows. From the time I’ve spent trying to understand why they do this, it seems that only one row of the grid is active at a time, with other rows collapsing due to empty content or missing HTML elements.
Interestingly, the CSS above is my simplified version. The original version looked like this, with the team using the grid-template
shorthand.
Below are the relevant menu items I can find in the application.
This CSS grid is designed for this widget and I think using CSS grid here is an over design.
Below is an example using a grid.
.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; }
Note that the gray portion of the CSS grid is inactive. They are collapsed because they have no content. For this specific example, the author could also do this:
.checkmark {<!-- --> align-self: start; grid-area: checkmarkArea; } #label {<!-- --> grid-area: labelArea; } ::slotted([slot="value"]) {<!-- --> grid-area: valueArea; }
There is no need to define the start and end of each column and row when they are the same value.
Extensive use of CSS variables
I really like how CSS variables can be used to change the UI. I will highlight several examples of this.
Change the size of layer thumbnails
If you are familiar with Photoshop, you can control the thumbnail size and make it smaller. This is useful when you have many layers and want to view more layers in less space.
First of all, there is an HTML attribute large-thumbs
on the main container of the layer panel.
<psw-layers-panel large-thumbs></psw-layers-panel>
In CSS, there is :host([large-thumbs])
to assign specific CSS variables.
: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 ); }
For each layer, there is an element named psw-layer-thumbnail
. This is where CSS variables will be applied. It will inherit it from the main container.
<psw-layers-panel-item> <psw-tree-view-item> <psw-layer-thumbnail class="thumb"></psw-layer-thumbnail> </psw-tree-view-item> </psw-layers-panel-item>
Here CSS variables are assigned to thumbnails.
: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 progress bar
Managing the size of a component is done using the size
property, a CSS variable that changes based on the 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 ); }
Image control
If the HTML attribute quite
is present, the UI is simpler.
This can also be achieved through CSS variables.
: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. */ }
Radio button
In this example, the team uses CSS variables to change the size of a radio button based on the size
HTML attribute.
<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. */ }
Lock page when menu is active
When the main menu is active, there is a “holder” element that fills the entire screen and is located below the menu.
#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; }
This element is used to prevent users from clicking or hovering over other parts of the page, appearing to mimic a desktop application.
Mixed Mode Menu
I discovered the use of the CSS viewport
unit here. The maximum height of a blended mode menu is 55vh
.
sp-menu {<!-- --> max-height: 55vh; --mod-menu-item-min-height: auto; } ::slotted(*) {<!-- --> overscroll-behavior: contain; }
overscroll-behavior: contain
is also useful. This is a great feature to avoid scrolling the body content.
Annotation component
Users can pin notes or drawings anywhere on the canvas. I inspected the component to see how it was built.
I like dynamic positioning and color CSS variables
To place each comment at the location chosen by the user, the team used CSS variables provided via JS.
<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); }
Use SVG for engineering drawing annotation
When you make the image smaller, the SVG strokes don’t resize and look thick.
As far as I know, this can be solved by adding vector-effect: non-scaling-stroke
. But I haven’t tried it.
Use Object-Fit: Contain for layer thumbnails
In the Layers panel, use object-fit: contain
for thumbnails to avoid distortion.
Finally
The article ends here, introducing some CSS techniques used in the web version of Photoshop. Compared with common domestic CSS technology, there are many differences, many of which are worth learning and learning from. Of course this is only part of it, if you are interested, you can check out their source code to delve deeper.
If you find this article useful, remember to give it a like and support it. You may use it someday if you save it~
Focus on front-end development, share front-end related technical information, public account: Nancheng Big Front-end (ID: nanchengfe)