Game Development 17 min read

Master Multi-Device UI Adaptation in Cocos with 9‑Slice Scaling

Learn how to achieve seamless multi‑device UI adaptation in Cocos by understanding design versus screen resolution, using Fit Height/Width modes, leveraging Widget components for edge alignment, and applying 9‑slice slicing to option backgrounds, all without cumbersome CSS or extensive code.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Master Multi-Device UI Adaptation in Cocos with 9‑Slice Scaling
Frontend developers often rely on media queries or rem for multi‑device adaptation, but CSS does not exist in Cocos. This article explains how to achieve multi‑device adaptation in Cocos from the requirement background.

Background

One day a new requirement arrived with a design mockup that presented several challenges:

The background image must be the same source but display different regions on different devices.

The countdown, title bar, and minimize button distances to the edges differ across devices.

The option background image must stretch according to option length while keeping the corner radii unchanged.

If this adaptation were done with CSS, a large number of media queries would be required, leading to verbose and cumbersome style code. Since Cocos does not support CSS, we need a different approach.

Analysis

We split the adaptation process into three parts:

Multi‑device background image

Multi‑device edge nodes

Option background 9‑slice slicing

Multi‑device background image

What are design resolution and screen resolution? Design resolution is the baseline used by designers when creating scenes, usually iPhone 6's 667×375. Screen resolution is the actual resolution of the device at runtime. In Cocos the canvas size is typically set to the design resolution, and all nodes inherit its scaling.

Relationship between them? If they are equal, the canvas fits the screen without scaling. If the screen is larger (e.g., 1334×750), the canvas must be scaled up, and all nodes benefit from intelligent scaling based on the design resolution.

Fit Height and Fit Width When the aspect ratios differ, Cocos Canvas provides two modes: Fit Height and Fit Width. The following diagrams illustrate the behavior on iPad (screen ratio < design ratio) and iPhone X (screen ratio > design ratio). Fit Height on iPad (screen ratio < design ratio): the canvas height fills the screen, and the sides are cropped. Fit Width on iPad: the canvas width fills the screen, and extra background appears at top and bottom. Fit Height on iPhone X (screen ratio > design ratio): the canvas height fills the screen, and extra background appears on the sides. Fit Width on iPhone X: the canvas width fills the screen, and the top and bottom are cropped. Choosing the mode based on the aspect ratio can be implemented as follows:

<code>export function setCanvasScaleMode(canvas: cc.Canvas) {
  const standardRadio = 16 / 9; // typical iPhone 6 ratio
  const screenSize = cc.view.getFrameSize();
  const currentRadio = screenSize.width / screenSize.height;
  if (currentRadio <= standardRadio) {
    // iPad‑like screens
    canvas.fitHeight = false;
    canvas.fitWidth = true;
  } else {
    // iPhone X‑like screens
    canvas.fitWidth = false;
    canvas.fitHeight = true;
  }
}
</code>

To avoid black borders, the background image should be larger than the design resolution and leave enough margin on all sides. Special cases PC and iPhone 7 share the same aspect ratio, but the design requires the PC version to display more background while keeping node sizes equal. Therefore a separate scaling step is applied for the PC platform.

Multi‑device edge nodes

What is the Widget component? Widget is a UI layout component in Cocos that aligns a node to any side or center of its parent. It is the ideal tool for edge alignment.

Which node should be the parent for edge alignment? The canvas node, because after applying Fit Height/Width it matches the screen size.

Setting edge distances per device Determine the current platform, then adjust the Widget's top/left values accordingly. A reusable UIAdaptor script encapsulates this logic:

<code>@ccclass
export default class UIAdaptor extends cc.Component {
  @property({type: Number, tooltip: `app: ${PlatformTypes.APP}, iPad: ${PlatformTypes.iPad}, PC: ${PlatformTypes.PC}, iPhoneX: ${PlatformTypes.iPhoneX}, AndroidiPad: ${PlatformTypes.AndroidiPad}`})
  platform: PlatformTypes = -1;

  @property({type: [Number], tooltip: 'left, top, right, bottom, horizontalCenter, verticalCenter'})
  widgetOptions: number[] = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];

  @property({type: Number, tooltip: `ONCE: ${WidgetAlignModes.ONCE}, ON_WINDOW_RESIZE: ${WidgetAlignModes.ON_WINDOW_RESIZE}, ALWAYS: ${WidgetAlignModes.ALWAYS}`})
  widgetAlignModes = WidgetAlignModes.ON_WINDOW_RESIZE;

  @property({type: [Number], tooltip: 'width, height'})
  size: number[] = [];

  @property({type: [Number], tooltip: 'x, y'})
  scale: number[] = [];

  @property({type: Number})
  fontSize = -1;

  private fitWidget() {
    const widget = this.getComponent(cc.Widget);
    if (!widget) return;
    enum WidgetOptions { left, top, right, bottom, horizontalCenter, verticalCenter }
    this.widgetOptions.forEach((value, index) => {
      if (typeof value !== 'number' || value >= Number.MAX_SAFE_INTEGER - 1) return;
      const optionType = WidgetOptions[index];
      widget[optionType] = value;
      widget[`isAlign${optionType.replace(optionType[0], optionType[0].toUpperCase())}`] = true;
    });
    widget.alignMode = this.widgetAlignModes;
  }

  private fitSize() {
    const [width, height] = this.size;
    this.node.setContentSize(width, height);
  }

  private fitFontSize() {
    const label = this.getComponent(cc.Label);
    label.fontSize = this.fontSize;
  }

  private fitScale() {
    const { scaleX: originalScaleX, scaleY: originalScaleY } = this.node;
    const [x, y = x] = this.scale;
    this.node.setScale(originalScaleX * x, originalScaleY * y);
  }

  private fit() {
    if (this.size.length) this.fitSize();
    if (this.fontSize >= 0) this.fitFontSize();
    if (this.scale.length) this.fitScale();
    this.fitWidget();
    const widget = this.getComponent(cc.Widget);
    if (widget) widget.updateAlignment();
  }

  start() {
    const platform = getPlatformType();
    if (platform === this.platform) {
      this.fit();
    }
  }
}
</code>

Applying the UIAdaptor component to each edge node enables code‑free multi‑device alignment.

Option background 9‑slice slicing

Why slice? Stretching a plain background image distorts the corner radii, resulting in an odd shape. 9‑slice slicing preserves the corners while allowing the middle sections to stretch.

What is 9‑slice? Cocos Creator provides a 9‑slice editor where green lines define the nine regions. Corners remain fixed; edges stretch horizontally or vertically. After slicing, the option button maintains its rounded corners regardless of length.

Tips Choose the asset scale (1×, 2×, 3×) that matches the design resolution. Using a 1× image makes corners too small; a 3× image makes them too large.

References

Multi‑resolution adaptation solutions

Creating stretchable UI images

widgetCocosUI scaling9-sliceFit HeightFit Widthmulti-device adaptation
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.