When implementing nested ScrollViews
in Cocos Creator—where the parent ScrollView is vertical (e.g., a list or menu), and the child ScrollView is horizontal (e.g., a row of items)—gesture conflicts can occur. Specifically:
- Vertical scrolling may stop working when the drag starts in the horizontal ScrollView area.
- Drag gestures may be incorrectly interpreted as touch/click events, resulting in unexpected behavior.
To resolve this, a custom script can be added to the horizontal ScrollView node. This script intelligently detects the drag direction and temporarily disables the horizontal scroll when a vertical drag is detected, allowing the parent (vertical) ScrollView to continue scrolling smoothly.
How to Use
- Attach the script (e.g.,
ScrollDirectionLocker.ts
) to your horizontal ScrollView node. - In the Inspector, assign the reference of the vertical ScrollView node to the script’s exposed property.
This setup ensures smooth gesture handling and eliminates conflicts between vertical and horizontal scroll directions.
const { ccclass, property } = cc._decorator;
@ccclass
export default class ScrollDirectionLocker extends cc.Component {
@property(cc.Node)
verticalScrollContent: cc.Node = null;
private scrollView: cc.ScrollView = null;
private startPos: cc.Vec2 = null;
private currentPos: cc.Vec2 = null;
private maxDragSqr: number = 0;
private isDragging: boolean = false;
private isVertical: boolean = false;
private dragThresholdSqr: number = 1;
protected start(): void {
let newHeight=cc.Canvas.instance.node.height;
let newWidth=cc.Canvas.instance.node.height;
let ratio=1;
if(newWidth/newHeight>ratio){
newWidth=newHeight*ratio;
}else{
newHeight=newHeight/ratio;
}
this.dragThresholdSqr=(1/100)*newHeight;
}
onLoad() {
this.scrollView = this.getComponent(cc.ScrollView);
this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this, true);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this, true);
}
onDestroy() {
this.node.off(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
this.node.off(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
this.node.off(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this, true);
this.node.off(cc.Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this, true);
}
onTouchStart(event: cc.Event.EventTouch) {
this.startPos = event.getLocation();
this.currentPos = this.startPos.clone();
this.maxDragSqr = 0;
this.isDragging = true;
this.isVertical = false;
if (this.scrollView) {
this.scrollView.enabled = true;
}
}
onTouchMove(event: cc.Event.EventTouch) {
this.currentPos = event.getLocation();
const delta = this.currentPos.sub(this.startPos);
const dragSqr = delta.magSqr();
if (!this.isVertical && dragSqr > this.dragThresholdSqr) {
this.isVertical = Math.abs(delta.y) > Math.abs(delta.x);
if (this.isVertical && this.scrollView) {
this.scrollView.enabled = false; // Disable horizontal drag
}
}
if (this.isVertical && this.verticalScrollContent) {
this.verticalScrollContent.dispatchEvent(event);
}
}
update(dt: number) {
if (this.isDragging && this.startPos && this.currentPos) {
const delta = this.currentPos.sub(this.startPos);
const dragSqr = delta.magSqr();
this.maxDragSqr = Math.max(this.maxDragSqr, dragSqr);
}
}
onTouchEnd(event: cc.Event.EventTouch) {
if (this.maxDragSqr > this.dragThresholdSqr) {
event.stopPropagation();
cc.log("Dragging occurred - click suppressed");
}
this.startPos = null;
this.currentPos = null;
this.maxDragSqr = 0;
this.isDragging = false;
this.isVertical = false;
if (this.scrollView) {
this.scrollView.enabled = true;
}
}
}