Skip to main content

Cross Flicking

Use the nested option to create a 2D cross-navigation by nesting an inner (horizontal) Flicking inside an outer (vertical) Flicking.

import Flicking from "@egjs/flicking";
import "@egjs/flicking/dist/flicking.css";
import "./styles.css";

const GROUPS = [
  { name: "Group A", colors: ["#e74c3c", "#c0392b", "#e67e22"] },
  { name: "Group B", colors: ["#3498db", "#2980b9", "#1abc9c"] },
  { name: "Group C", colors: ["#2ecc71", "#27ae60", "#16a085"] }
];

const outerCamera = document.querySelector("#outer .flicking-camera");

GROUPS.forEach((group, gi) => {
  const outerPanel = document.createElement("div");
  outerPanel.className = "outer-panel";

  const label = document.createElement("div");
  label.className = "group-label";
  label.textContent = `${group.name} (swipe vertically to switch groups)`;
  outerPanel.appendChild(label);

  const innerViewport = document.createElement("div");
  innerViewport.className = "flicking-viewport";
  innerViewport.id = `inner-${gi}`;
  const innerCamera = document.createElement("div");
  innerCamera.className = "flicking-camera";
  innerViewport.appendChild(innerCamera);

  group.colors.forEach((color, pi) => {
    const panel = document.createElement("div");
    panel.className = "inner-panel";
    panel.style.background = color;
    panel.innerHTML = `<span>${group.name}-${pi + 1}</span><span class="panel-subtitle">swipe horizontally</span>`;
    innerCamera.appendChild(panel);
  });

  outerPanel.appendChild(innerViewport);
  outerCamera.appendChild(outerPanel);
});

const outerFlicking = new Flicking("#outer", {
  horizontal: false,
  moveType: "strict",
  bound: true,
  align: "prev"
});

GROUPS.forEach((_, gi) => {
  new Flicking(`#inner-${gi}`, {
    nested: true,
    moveType: "strict",
    bound: true,
    align: "prev"
  });
});

const infoBar = document.querySelector(".info-bar");
outerFlicking.on("changed", e => {
  infoBar.textContent = `Current group: ${GROUPS[e.index].name} (vertical: switch groups / horizontal: navigate within group)`;
});

Summary

Key Options

OptionScopeValueRole
horizontalOuterfalseVertical direction (navigation between groups)
nestedInnertruePropagate events to outer when end is reached
moveTypeBoth"strict"Move exactly one unit at a time

Structure

ComponentDirectionRole
Outer FlickingVertical (↕)Navigation between groups
Inner FlickingHorizontal (↔)Navigation within a group

Details

Role of the nested Option

When nested: true is set on the inner Flicking, swiping in the same direction after reaching the end of the inner Flicking propagates the event to the outer Flicking. In this demo, the outer and inner directions are different (vertical/horizontal), so input is naturally separated.

Direction Separation

Horizontal swipes are handled by the inner Flicking, and vertical swipes are handled by the outer Flicking. For same-direction nesting (e.g., horizontal inside horizontal), nested: true manages event propagation.

  • Relationship between nested and horizontal: Different directions work without conflict; for the same direction, nested: true controls propagation
  • Combination with moveType: "strict": Makes group/panel unit navigation clear
  • Combination with bound: true: Prevents empty space at each level

Use Cases

When should you use this?
  • Category-based content (vertical for categories, horizontal for items)
  • Photo albums (vertical for albums, horizontal for photos)
  • Dashboards (vertical for sections, horizontal for cards)

Notes

Caution
  • When the outer Flicking is vertical (horizontal: false), you must specify a height on the viewport.
  • If nested: true is not set on the inner Flicking, events are consumed internally and not propagated to the outer Flicking.
  • When nesting in the same direction, it may be difficult to distinguish swipe gestures.