Section.onViewportEnter() not working

I’m having trouble with
Lazy loading page sections using Velo code and the onViewportEnter event—the event isn’t firing for collapsed sections.

Working in
Wix Studio Editor, Dev Mode (Velo)

Site link
https://www.edhike.in/

What I’m trying to do
I want to implement lazy loading for multiple page sections. My goal is for sections (e.g., section1, section2, etc.) to reveal with a smooth animation only when a user scrolls them into view. I’m using Velo’s onViewportEnter for each section component to trigger the expand/show action.

What I’ve tried so far
Collapsing all sections (except the hero) on page load, then adding section.onViewportEnter(() => { section.expand() }) to each.

  • Using both hide() and collapse()—neither triggers onViewportEnter as expected.

  • Checked Velo documentation and forum posts; most examples either use visible sections or a different triggering mechanism.

  • Tried scroll handlers and Intersection Observers, but they complicate the layout handling for true sections.

  • Added console logs to verify the callback, but it’s never reached for collapsed sections.

Extra context
It seems like sections that are collapsed, and therefore not rendered in the DOM/layout flow, cannot trigger the onViewportEnter event. Ideally, I’d like to keep sections out of the DOM for performance (using collapsed), but still reveal them on scroll with an animation and minimal initial page weight. Has anyone found a reliable pattern for this? Any official clarification on viewport events and collapsed sections would help. Example code and edge-case solutions appreciated!

Here is code I am using inside velo code editor for a particular page :
$w.onReady(function () {
const sections = $w(‘Section’);
if (sections.length === 0) return;

const [firstSection, …otherSections] = sections;
otherSections.forEach((section, index) => {
if (!section.hidden) section.hide(); // hide, not collapse
});

otherSections.forEach((section, idx) => {
section.onViewportEnter(() => {
setTimeout(() => {
section.show(‘fade’, { duration: 600 });
}, idx * 200);
});
});
});

Well, first of all the first issue i do recognize within seconds is…
if (ections.length === 0) return;
What’s wrong here?

Furthermore, let’s do some brainstorming first and clarifying your situation.

Problem:

  • heavy section loadings you want to reduce on start (by generating a lazy loading → onScroll, or onViewPortEnter) —> WHAT??? —> onScroll()???

But there is no → onScroll() ← to be found inside the VELO-API !!!

And yes that’s right!!! But wait, what’s that? —>

Maybe a possible solution?

Ok, let’s try even another approach!
Let’s say, your statement is right and once an element is collapsed it can’t be expanded on → viewPortEnter() ←

:cross_mark: Why onViewportEnter() doesn’t fire for collapsed sections

  • When you collapse a section in Wix Velo, it’s removed from the page’s render/layout flow.
  • In other words, it has no bounding box — no height, no position, no intersection to detect.
  • The browser (and Velo’s internal observer) literally can’t tell when it enters the viewport, because as far as the page layout is concerned, it doesn’t exist visually.

So onViewportEnter() can’t possibly fire.
This is expected (but undocumented) behavior.

Let’s assume this is correct!

But what if you would put just 1px heigh line-element directly (above or under) each of your sections, it could be even a small → dot ← or dot-like image, or even a transparent one showing just 1pixel (corresponding to each of your sections. Section could still be → COLLAPSED ← but the INVISIBLE-DOT-PICS, which are barely 1kb big of size (wouldn’t really reduce site performance) → but would give you the ability to use → onViewPortEnter again, even if the rest of all other sections are collapsed, since the mechanism now is controlled by the invisible pic elements.

Here’s the reasoning:

  • You keep your heavy sections collapsed (saving rendering cost).
  • But you place a small invisible or 1px-high element above or below each collapsed section.
  • Those elements stay in the layout flow and can trigger onViewportEnter.
  • Once a trigger element enters view → you can expand and animate the actual section.

That way, the trigger element acts as a proxy observer for the collapsed section.

Just as an alternative idea.

  • I also couldn’t find any console-logs in your code.
  • And i also do not understand why you are doing the following —>
const [firstSection, …otherSections] = sections; 

Who is working like that?

Wouldn’t it be more simple and logical to treat all of found sections the same?

$w.onReady(function () {
    const sections = [
        $w('#section1'),
        $w('#section2'),
        $w('#section3')
    ];

    const triggers = [
        $w('#trigger1'),
        $w('#trigger2'),
        $w('#trigger3')
    ];

    // Collapse all sections except the first (e.g., hero)
    sections.slice(1).forEach(sec => sec.collapse());

    triggers.forEach((trigger, idx) => {
        trigger.onViewportEnter(() => {
            console.log(`Trigger for section ${idx + 1} entered viewport`);
            
            // Expand + animate section
            const target = sections[idx];
            if (target.collapsed) {
                target.expand();
                target.show('fade', { duration: 600 });
            }
        });
    });
});

Now, each “trigger” element can be:

  • A tiny line, a transparent box, or a 1px-high strip
  • Positioned right before or after its target section
  • Set to “collapsed on load = false”, “visible on load = true” (but can be fully transparent)

This is just the simplified version —>

    const sections = [
        $w('#section1'),
        $w('#section2'),
        $w('#section3')
    ];

…of…—>
const sections = $w('Section'); console.log('My-Available-Sections:', sections);

I already can feel the success…

3 Likes

Thanks a lot. I have implemented partial loading of the sections/components of a page successfully.

1 Like

What was your solution?