agentsclimarketplace

Cocos code driven scrollview ui

Skill ChrisLamDev/cocos-creator-debug-skills/skills/cocos-code-driven-scrollview-ui

Use when building code-driven scrollable UI panels in Cocos Creator 3.x.From its SKILL.md

Install
npx -y skills add ChrisLamDev/cocos-creator-debug-skills --skill cocos-code-driven-scrollview-ui

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.2 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Cocos Creator 3.x Code-Driven ScrollView UI Panel

When To Use

When you need to build a complex UI panel (ScrollView + TabBar + Header + interactive cards) in Cocos Creator 3.x without any Editor manual binding. The user only needs to create one empty Node in the scene and attach the script.

Best for:

  • Phase 7 content migration panels (Scripture Library, Story Engine, Recitation Hall)
  • Any temporary test UI that shouldn't clutter the Editor scene
  • Panels that need clean lifecycle management (create/destroy without leaks)

Architecture Pattern

File Structure (3-file module)

assets/scripts/ModuleName/
├── ModuleTypes.ts    — Constants, enums, state interfaces
├── ModuleManager.ts  — Singleton Cocos Component, data loading + query API
└── ModuleUI.ts       — Cocos Component, all dynamic node generation + rendering

Full Node Hierarchy (code-generated in onLoad)

ScripturePanel (ScriptureUI Component)
├── header (Node)
│   └── title (Label) — "📚 藏經閣"
├── tabBar (Node)
│   ├── tab_classic1 (Label) — "📜 無量壽經"
│   ├── tab_classic2 (Label) — "📜 觀經"
│   └── ...
├── scrollView (ScrollView)
│   └── view (UITransform + Mask Type.RECT)
│       └── content (UITransform + Layout Type.VERTICAL, ResizeMode.CONTAINER)
│           └── [dynamic child nodes: cards, headings, text, spacers]
└── closeBtn (Label) — "✕"

ScrollView Construction (Critical)

The correct node hierarchy for a working Cocos 3.x ScrollView is:

// 1. ScrollView outer node
const svNode = new Node('scrollView');
svNode.setPosition(x, y, 0);
parent.addChild(svNode);
const svTrans = svNode.addComponent(UITransform);
svTrans.width = PANEL_W;
svTrans.height = svH;

// 2. ScrollView component (not on child — on the outer node)
const scrollView = svNode.addComponent(ScrollView);
scrollView.horizontal = false;
scrollView.vertical = true;
scrollView.inertia = true;
scrollView.brake = 0.85;
scrollView.elastic = true;
scrollView.verticalScrollBar = null as any; // hide scrollbar

// ⚠️ CRITICAL: View node MUST have Mask for clipping
const viewNode = new Node('view');
viewNode.setPosition(0, 0, 0);
svNode.addChild(viewNode);
const viewTrans = viewNode.addComponent(UITransform);
viewTrans.width = PANEL_W;
viewTrans.height = svH;
const mask = viewNode.addComponent(Mask);
mask.type = Mask.Type.RECT;

// ⚠️ CRITICAL: Content anchor MUST be (0.5, 1) for vertical scroll from top
const contentNode = new Node('content');
contentNode.setPosition(0, svH / 2, 0); // top-center of view
viewNode.addChild(contentNode);
const contentTrans = contentNode.addComponent(UITransform);
contentTrans.width = PANEL_W - PADDING * 2;
contentTrans.height = svH; // initial, auto-resized by Layout
contentTrans.anchorX = 0.5;
contentTrans.anchorY = 1;

// ⚠️ CRITICAL: Layout with CONTAINER resize mode auto-grows content
const layout = contentNode.addComponent(Layout);
layout.type = Layout.Type.VERTICAL;
layout.resizeMode = Layout.ResizeMode.CONTAINER;
layout.horizontalDirection = Layout.HorizontalDirection.LEFT_TO_RIGHT;
layout.verticalDirection = Layout.VerticalDirection.TOP_TO_BOTTOM;
layout.paddingTop = 30;
layout.paddingLeft = PADDING;
layout.paddingRight = PADDING;
layout.spacingY = 20;

// Link scrollView.content
scrollView.content = contentNode;

ScrollView Pitfalls

MistakeSymptomFix
No Mask on View nodeContent overflows visible areaAdd viewNode.addComponent(Mask) with type = Mask.Type.RECT
Content anchor (0.5, 1) wrongContent starts from center instead of topSet contentTrans.anchorX = 0.5; anchorY = 1
Missing Layout CONTAINERContent doesn't auto-growAdd Layout, set resizeMode = CONTAINER
Setting content height manuallyLayout CONTAINER + manual height conflictDon't set content height — Layout handles it
Not linking scrollView.contentScrollView doesn't know what to scrollscrollView.content = contentNode
Forgetting as any on scrollBarnull assignment type errorscrollView.verticalScrollBar = null as any
scrollView placed on wrong nodeScrollView doesn't scrollScrollView component goes on the OUTER node, NOT the view node

Touch Dispatch Pattern

Since all nodes are code-generated, use node name string prefix for touch identification (no Editor event system):

// Constants for node name prefixes
const TAG_TAB = 'tab_';
const TAG_CARD = 'card_';
const TAG_BACK = 'btn_back';
const TAG_CLOSE = 'btn_close';

onLoad(): void {
  this.node.on(Node.EventType.TOUCH_END, this._onTouchEnd, this);
}

// When creating nodes
const tabNode = new Node(TAG_TAB + tab.id);  // e.g. "tab_classic1"
const cardNode = new Node(TAG_CARD + classic.id);  // e.g. "card_classic1"
backBtn.name = TAG_BACK;
closeBtn.name = TAG_CLOSE;

private _onTouchEnd(event: EventTouch): void {
  const target = event.target;
  if (!target) return;
  const name = target.name;

  if (name === TAG_CLOSE) { this.node.destroy(); return; }
  if (name === TAG_BACK) { this._showCatalogue(); return; }
  if (name.startsWith(TAG_TAB)) { ... }
  if (name.startsWith(TAG_CARD)) { ... }
}

Lifecycle Management

// Clean content (remove all children) when switching views
private _clearContent(): void {
  if (this._contentNode) {
    this._contentNode.removeAllChildren();
  }
}

// Clean full destruction
// When user clicks close: this.node.destroy()
// Cocos handles recursive child destruction

cc.d.ts Declarations Needed

If your project has a custom types/cc.d.ts for tsc --noEmit, you need these declarations:

// ===== ScrollView =====
export class ScrollView extends Component {
  public content: Node | null;
  public horizontal: boolean;
  public vertical: boolean;
  public inertia: boolean;
  public brake: number;
  public elastic: boolean;
  public verticalScrollBar: Node | null;
}

// ===== Layout =====
export namespace Layout {
  enum Type { NONE = 0, HORIZONTAL = 1, VERTICAL = 2, GRID = 3 }
  enum ResizeMode { NONE = 0, CONTAINER = 1, CHILDREN = 2 }
  enum HorizontalDirection { LEFT_TO_RIGHT = 0, RIGHT_TO_LEFT = 1 }
  enum VerticalDirection { TOP_TO_BOTTOM = 0, BOTTOM_TO_TOP = 1 }
}
export class Layout extends Component { ... }

// ===== Mask =====
export namespace Mask { enum Type { RECT = 0, ELLIPSE = 1, IMAGE_STENCIL = 2 } }
export class Mask extends Component { public type: Mask.Type; }

// ===== Node setPosition overload =====
// Accept both Vec3 and (x, y, z)
public setPosition(x: number | Vec3, y?: number, z?: number): void;

// ===== UITransform setContentSize overload =====
public setContentSize(w: number, h?: number): void;
public setContentSize(s: Size): void;

// ===== EventTouch =====
export class EventTouch { public target: Node; }

Label Creation Pattern (no isSystemFontUsed)

Cocos 3.x Label does NOT have isSystemFontUsed property in all versions. Use this safe pattern:

private _makeLabel(text: string, fontSize: number, hexColor: string): Node {
  const node = new Node('label');
  const label = node.addComponent(Label);
  label.string = text;
  label.fontSize = fontSize;
  label.lineHeight = fontSize * 1.6;
  label.color = this._hexToColor(hexColor);
  label.horizontalAlign = Label.HorizontalAlign.CENTER;
  const trans = node.getComponent(UITransform);
  if (trans) trans.width = CARD_W;
  return node;
}

private _hexToColor(hex: string): Color {
  return Color.fromHEX(new Color(), hex);
}

private _wrapText(text: string, charsPerLine: number): string {
  // Simple char-count wrapping
  let result = '', count = 0;
  for (const ch of text) { result += ch; count++; if (count >= charsPerLine) { result += '\n'; count = 0; } }
  return result;
}

Dark Mode Color Constants

Place at module top-level for easy global adjustment:

const C_BG = '#1A1A2E';
const C_SURFACE = '#16213E';
const C_TEXT_PRIMARY = '#F0EAD6';
const C_TEXT_SECONDARY = '#B8B0A0';
const C_GOLD = '#E8C96A';
const C_PINK = '#D98C7A';
const C_TAB_INACTIVE = '#4A5A7A';
const C_TAB_ACTIVE = '#E8C96A';

Steps to Add a New Code-Driven Panel

  1. Create Types.ts — Tab definitions, UI state interface, defaults
  2. Create Manager.ts — Singleton Component with data loading (async) + query API
  3. Create UI.ts — Component with:
    • onLoad(): buildHeader(), buildTabBar(), buildScrollView(), buildCloseButton()
    • Display methods: showCatalogue(), showClassicContent(), showDailySutra()
    • Helper methods: makeLabel(), makeSpacer(), wrapText(), hexToColor()
    • Touch handler: _onTouchEnd() with name-based dispatch
  4. Update Bootstrap.ts — Add import + switch case
  5. Update cc.d.ts — Add any missing Cocos API declarations
  6. Run tsc --noEmit — Confirm zero errors on new files
  7. Git commit
  8. User test: Open Creator → Preview → click button

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.