Scroll to required question
August 11, 2025 · AI Conversations
$(this).siblings('input').trigger('click'); // update GF conditional logic first
var scrollToEl = '';
var $sourceBlockEl = $(this).closest('.gfield');
var $nextBlockEl = $sourceBlockEl.next();
var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;
// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick = ((($(this).attr('onclick') || '').indexOf('incorrect') === -1) && (isOverride || (typeof availableProducts !== 'undefined' && availableProducts.length > 0)));
if (isCorrectClick) {
// 1) Try going UP to nearest previous incorrect/error
var $incorrectFields = $('.gfield:visible').filter(function(){
var $f = $(this);
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible').length > 0 ||
$f.find('.choice_incorrect:visible').length > 0;
});
if ($incorrectFields.length) {
var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
$incorrectFields.each(function(){
var $f = $(this);
if ($f.offset().top < srcTop) $prevIncorrect = $f; // last one above = nearest previous
});
if ($prevIncorrect.length) {
scrollToEl = $prevIncorrect;
// mobile centering tweak for tall fields
if (scrollToEl.outerHeight() > $(window).outerHeight()) {
var $choice = scrollToEl.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if ($choice.length) scrollToEl = $choice;
headerHeight = $(window).outerHeight() / 4;
}
}
}
// 2) If no incorrect above, fall back to normal DOWN auto-scroll
if (!scrollToEl) {
// next visible unanswered block (fix typo: .length)
if ($nextBlockEl.is(':visible')
&& $nextBlockEl.find('input:checked').length === 0
&& $nextBlockEl.find('.choice_selected').length === 0) {
scrollToEl = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
var found = false;
$('.gfield:visible').each(function(){
var $f = $(this);
if ($f.find('.ginput_container').length === 0) return; // skip HTML/section
var answered = false;
if ($f.find('.ginput_container_choice').length) {
answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
} else {
answered =
$f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0
|| $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0
|| $f.find('input[type="date"], input[type="time"]').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
}
if (!answered) { scrollToEl = $f; found = true; return false; }
});
if (!found) {
// nothing left unanswered → center submit
scrollToEl = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks (per your requirement)
if (scrollToEl) {
var elOffset = $(scrollToEl).is(':visible')
? $(scrollToEl).offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();
$('html, body').animate({ scrollTop: elOffset – headerHeight }, 1000);
}
});
also allow to scroll up if correct choice seleced that required question user no given anser
Drop-in replacement below. I added small helpers to detect **incorrect**, **required**, and **answered**. The “previous target” now picks the nearest field above that is either **incorrect** OR **required & unanswered**. Everything else (downward scroll, submit centering, header offset) stays the same.
$('.gfield_choice:not(:last) label').on('click', function () {
$(this).siblings('input').trigger('click'); // update GF conditional logic first
var $scrollTo = null;
var $sourceBlockEl = $(this).closest('.gfield');
var $nextBlockEl = $sourceBlockEl.next();
var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;
// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick = ((($(this).attr('onclick') || '').indexOf('incorrect') === -1) &&
(typeof isOverride !== 'undefined' && isOverride ||
(typeof availableProducts !== 'undefined' && availableProducts.length > 0)));
// --- helpers ---
function isIncorrect($f){
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f){
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputContainer($f){
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f){
if (!hasInputContainer($f)) return true; // sections/HTML treated as “answered/skip”
// choices
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}
// text-ish
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dateTimeFilled;
}
// --- end helpers ---
if (isCorrectClick) {
// 1) Go UP to nearest previous field that is incorrect OR (required & unanswered)
var $visibleFields = $('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (isIncorrect($f) || (isRequired($f) && !isAnswered($f))) {
$scrollTo = $f;
break;
}
}
// For tall targets on mobile, try to focus a meaningful sub-element
if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) {
$sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
}
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4;
}
// 2) If no target above, fall back to normal DOWN auto-scroll
if (!$scrollTo) {
if ($nextBlockEl.is(':visible') && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
$('.gfield:visible').each(function(){
var $f = $(this);
if (!hasInputContainer($f)) return; // skip non-input sections
if (!isAnswered($f)) { $scrollTo = $f; return false; }
});
if (!$scrollTo) {
// nothing left unanswered → center submit
$scrollTo = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();
$('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
}
});
This keeps your current behavior, **adds** the “scroll up to nearest required-but-unanswered” when you pick a correct answer, and still never scrolls on incorrect clicks.
// 1) Go UP to nearest previous field that is REQUIRED and
// (incorrect OR unanswered)
var $visibleFields = $('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
$scrollTo = $f;
break;
}
}
Keep your helpers as-is (or like below):
function isIncorrect($f){
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f){
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function isAnswered($f){
if (!$f.find('.ginput_container').length) return true;
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
var dtFilled = $f.find('input[type="date"], input[type="time"]').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dtFilled;
}
Now it will only scroll up to **required** questions (skips non-required), even if they’re incorrect/unanswered.
// Auto-scroll on correct choice
jQuery(function($){
$('.gfield_choice:not(:last) label').on('click', function () {
// Let GF update conditional logic first
$(this).siblings('input').trigger('click');
var $scrollTo = null;
var $sourceBlockEl = $(this).closest('.gfield');
var $nextBlockEl = $sourceBlockEl.next();
// Safe header offset (Divi)
var headerHeight = 0;
try {
headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : ($('#et-main-area').offset() ? $('#et-main-area').offset().top : 0);
} catch(e){ headerHeight = 0; }
// Treat click as "correct" only if not flagged incorrect AND gating allows
var isCorrectClick =
((($(this).attr('onclick') || '').indexOf('incorrect') === -1) &&
( (typeof window.isOverride !== 'undefined' && window.isOverride) ||
(Array.isArray(window.availableProducts) && window.availableProducts.length > 0) ));
// --- helpers ---
function isIncorrect($f){
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f){
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputContainer($f){
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f){
if (!hasInputContainer($f)) return true; // sections/HTML => skip as "answered"
// choice fields
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}
// text-like
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dateTimeFilled;
}
// --- end helpers ---
if (isCorrectClick) {
// 1) Go UP to nearest previous field that is REQUIRED and (incorrect OR unanswered)
var $visibleFields = $('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
$scrollTo = $f;
break;
}
}
// For tall targets on mobile, try a meaningful sub-element
if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4;
}
// 2) If no required target above, fall back to normal DOWN auto-scroll
if (!$scrollTo) {
if ($nextBlockEl.is(':visible') && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
$('.gfield:visible').each(function(){
var $f = $(this);
if (!hasInputContainer($f)) return; // skip non-input sections
if (!isAnswered($f)) { $scrollTo = $f; return false; }
});
if (!$scrollTo) {
// nothing left unanswered → center submit
$scrollTo = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();
$('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
}
});
});
**What changed vs your last snippet**
– UP loop now targets **only required** fields: `isRequired($f) && (isIncorrect($f) || !isAnswered($f))`.
– Safer checks for globals (`window.isOverride`, `window.availableProducts`).
– Guarded header offset.
– binds via delegation (works after GF re-renders),
– waits a tick so GF conditional logic updates first,
– only scrolls **up** to **required** fields (incorrect or unanswered),
– keeps your down-scroll fallback.
Drop this in:
// Gravity Forms auto-scroll on correct choice
jQuery(function ($) {
// delegated binding survives GF DOM re-renders
$(document).on('click', '.gfield_choice label', function () {
// let GF toggle inputs & conditional logic first
var $label = $(this);
var $input = $label.siblings('input');
$input.trigger('click');
// run after GF updates DOM (tiny delay is important)
setTimeout(function () {
var $scrollTo = null;
var $sourceBlockEl = $label.closest('.gfield');
// find the next visible input field (skip sections/HTML)
function nextInputField($from) {
var $n = $from.nextAll('.gfield:visible').filter(function () {
return $(this).find('.ginput_container').length > 0;
}).first();
return $n.length ? $n : $();
}
var $nextBlockEl = nextInputField($sourceBlockEl);
// header offset (Divi safe)
var headerHeight = 0;
try {
var $main = $('#et-main-area');
headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : ($main.length ? $main.offset().top : 0);
} catch (e) { headerHeight = 0; }
// ---- helpers ----
function isIncorrect($f) {
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f) {
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputs($f) {
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f) {
if (!hasInputs($f)) return true;
// choice fields
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}
// text/select/date-like
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
var selectFilled = $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0;
var dtFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dtFilled;
}
// consider the click "correct" only if label isn't marked incorrect
// and gating passes (defaults to true if globals are absent)
function isCorrectClick() {
var attr = ($label.attr('onclick') || '');
var notIncorrect = attr.indexOf('incorrect') === -1;
var gating =
(typeof window.isOverride !== 'undefined' && !!window.isOverride) ||
(Array.isArray(window.availableProducts) ? window.availableProducts.length > 0 : true);
return notIncorrect && gating;
}
// ---- /helpers ----
if (isCorrectClick()) {
// 1) UP: nearest REQUIRED field above that is incorrect OR unanswered
var $visibleFields = $('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
$scrollTo = $f;
break;
}
}
// Tall target? aim at a sub-element for better centering
if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4;
}
// 2) No required target above → normal DOWN auto-scroll
if (!$scrollTo || !$scrollTo.length) {
if ($nextBlockEl.length && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
$('.gfield:visible').each(function () {
var $f = $(this);
if (!hasInputs($f)) return; // skip non-input sections
if (!isAnswered($f)) { $scrollTo = $f; return false; }
});
if (!$scrollTo || !$scrollTo.length) {
// nothing left unanswered → center submit/footer
$scrollTo = $('.gform_footer').first();
if ($scrollTo.length) headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}
// Only scroll on "correct" clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();
$('html, body').animate({ scrollTop: elOffset - headerHeight }, 600);
}
}, 40); // tiny delay so GF logic finishes
});
});
**Why this should fix it**
– Uses delegated binding (`$(document).on('click', …)`) so the handler still works after GF redraws fields.
– Waits 40ms before computing targets so conditional logic has updated the DOM.
– `isCorrectClick()` now defaults gating to true if your globals aren’t present, so it won’t silently block scrolling.
– UP scan restricted to **required** only, exactly as you wanted.
If it’s still not moving, quickly check in DevTools:
// while clicking a label, see what this prints
window.gfdbg = true;
…and temporarily add `console.log({correct:isCorrectClick()})` inside the handler to verify the gating. If that logs `false`, your `onclick` doesn’t contain “incorrect” or the gating vars are off—easy to tweak.
– waits for GF conditional logic to finish,
– detects “correct” via quiz classes (more reliable than `onclick`),
– scrolls **up only to REQUIRED** fields (incorrect or unanswered),
– finds the *actual* scrollable container and scrolls it.
// Gravity Forms: auto-scroll on correct choice
// - UP only to REQUIRED (incorrect or unanswered)
// - DOWN fallback to next unanswered
// - Robust scroller detection (Divi/Elementor/etc.)
jQuery(function ($) {
// delegated binding = survives GF DOM changes
$(document).on('click', '.gfield_choice label', function () {
var $label = $(this);
// Let native label→input click + GF conditional logic run first
setTimeout(function run() {
var $scrollTo = $();
var $source = $label.closest('.gfield');
// ---------- helpers ----------
function isIncorrect($f) {
// field flagged incorrect OR has visible incorrect choice
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f) {
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputs($f) {
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f) {
if (!hasInputs($f)) return true; // sections/HTML
// choice fields
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}
// text-like
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
var selectFilled = $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0;
var dtFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dtFilled;
}
function nextInputField($from) {
var $n = $from.nextAll('.gfield:visible').filter(function () {
return hasInputs($(this));
}).first();
return $n.length ? $n : $();
}
function getHeaderOffset() {
try {
if ($('#et_mobile_nav_menu').is(':visible')) return 0;
var $main = $('#et-main-area');
return $main.length ? ($main.offset().top || 0) : 0;
} catch (e) { return 0; }
}
function getScroller() {
// try likely containers; pick the first that actually scrolls
var candidates = [
'html, body',
'#page-container',
'.et_builder_inner_content',
'.et_pb_section',
'main',
'.site-main'
];
for (var i = 0; i < candidates.length; i++) {
var $el = $(candidates[i]);
if (!$el.length) continue;
var t = $el.scrollTop();
$el.scrollTop(t + 1);
var moved = $el.scrollTop() !== t;
$el.scrollTop(t);
if (moved) return $el;
}
return $('html, body');
}
function smoothScrollTo($target, offset) {
var top = ($target.offset() ? $target.offset().top : 0) - offset;
if ('scrollBehavior' in document.documentElement.style) {
// native smooth scroll
window.scrollTo({ top: top, behavior: 'smooth' });
} else {
// jQuery fallback on the real scroller
getScroller().animate({ scrollTop: top }, 600);
}
}
function isCorrectClick() {
// Prefer quiz classes over brittle onclick text
var isMarkedIncorrect = $label.is('.gquiz-incorrect-choice, .choice_incorrect') ||
$label.closest('li, .gchoice').is('.gquiz-incorrect-choice, .choice_incorrect');
// gating: if globals exist, use them; otherwise don't block
var gating =
(typeof window.isOverride !== 'undefined' ? !!window.isOverride : true) &&
(typeof window.availableProducts !== 'undefined'
? (Array.isArray(window.availableProducts) ? window.availableProducts.length > 0 : !!window.availableProducts)
: true);
return !isMarkedIncorrect && gating;
}
// ---------- /helpers ----------
if (!isCorrectClick()) {
return; // no scroll on incorrect/blocked click
}
var headerOffset = getHeaderOffset();
// 1) UP: nearest REQUIRED field above that is incorrect OR unanswered
var $visible = $('.gfield:visible');
var idx = $visible.index($source);
for (var i = idx - 1; i >= 0; i--) {
var $f = $visible.eq(i);
if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
$scrollTo = $f;
break;
}
}
// For tall fields, aim at a visible control
if ($scrollTo.length && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
if ($sub.length) $scrollTo = $sub;
headerOffset = $(window).outerHeight() / 4;
}
// 2) No required target above → DOWN to next unanswered
if (!$scrollTo.length) {
var $next = nextInputField($source);
if ($next.length && !isAnswered($next)) {
$scrollTo = $next;
} else {
// first visible unanswered anywhere (required OR optional)
$('.gfield:visible').each(function () {
var $f = $(this);
if (!hasInputs($f)) return; // skip non-input sections
if (!isAnswered($f)) { $scrollTo = $f; return false; }
});
if (!$scrollTo.length) {
// nothing left → center submit/footer
$scrollTo = $('.gform_footer:visible').first();
if ($scrollTo.length) headerOffset = headerOffset + ($(window).outerHeight() / 4);
}
}
}
if ($scrollTo.length) {
smoothScrollTo($scrollTo, headerOffset);
} else {
// last resort: ensure at least some motion so you can tell it runs
// comment this out later if not needed
// getScroller().animate({ scrollTop: ($(window).scrollTop() + 1) }, 1);
}
}, 120); // a bit more time for GF conditional logic to finish
});
});
**If it still doesn’t move**, quick checks:
1. In DevTools console, run `window.gfdbg=true` and temporarily add:
console.log('correct?', isCorrectClick());
inside the handler to ensure it’s `true` on your expected clicks.
2. Verify what scrolls on your page: in console run:
(function(){var els=['html, body','#page-container','.et_builder_inner_content','main']; els.forEach(s=>{var $e=jQuery(s); if($e.length){var t=$e.scrollTop(); $e.scrollTop(t+1); console.log(s,'moves?', $e.scrollTop()!==t); $e.scrollTop(t);}})();
If only `#page-container` moves, the helper will pick it automatically.