Skip to content

Commit b191b83

Browse files
committed
feat: move scroll to top to free
1 parent 1b521a8 commit b191b83

File tree

12 files changed

+1320
-81
lines changed

12 files changed

+1320
-81
lines changed

assets/apps/customizer-controls/src/controls.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,50 @@ const checkHasElementorTemplates = () => {
297297
}
298298
};
299299

300+
/**
301+
* Find the Scroll to top button within the customizer preview.
302+
*/
303+
function findScrollToTopBtn() {
304+
const iframeElement = document.querySelector('#customize-preview iframe');
305+
306+
if (!iframeElement) {
307+
return;
308+
}
309+
310+
const scrollToTopBtn =
311+
iframeElement.contentWindow.document.querySelector('#scroll-to-top');
312+
313+
return scrollToTopBtn;
314+
}
315+
316+
/**
317+
* Show the Scroll to Top button as soon as the user enters the section in Customizer.
318+
*/
319+
function previewScrollToTopChanges() {
320+
wp.customize.section('neve_scroll_to_top', (section) => {
321+
section.expanded.bind((isExpanded) => {
322+
const scrollToTopBtn = findScrollToTopBtn();
323+
324+
if (!scrollToTopBtn) {
325+
return;
326+
}
327+
328+
// If Scroll to top customizer section is expanded
329+
if (isExpanded) {
330+
wp.customize.previewer.bind('ready', () => {
331+
wp.customize.previewer.send('nv-opened-stt', true);
332+
});
333+
scrollToTopBtn.style.visibility = 'visible';
334+
scrollToTopBtn.style.opacity = '1';
335+
} else {
336+
// Hide the button when we leave the section
337+
scrollToTopBtn.style.visibility = 'hidden';
338+
scrollToTopBtn.style.opacity = '0';
339+
}
340+
});
341+
});
342+
}
343+
300344
window.wp.customize.bind('ready', () => {
301345
initStarterContentNotice();
302346
initDocSection();
@@ -311,6 +355,7 @@ window.wp.customize.bind('ready', () => {
311355
initBlogPageFocus();
312356
initSearchCustomizer();
313357
initLocalGoogleFonts();
358+
previewScrollToTopChanges();
314359
});
315360

316361
window.HFG = {

assets/js/src/customizer-preview/app.js

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/src/scroll-to-top.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*global neveScrollOffset*/
2+
3+
function scrollTopSafe(to) {
4+
let i = window.scrollY;
5+
to = parseInt(to);
6+
const scrollInterval = setInterval(function () {
7+
if (i < to + 20) i -= 1;
8+
else if (i < to + 40) i -= 6;
9+
else if (i < to + 80) i -= 16;
10+
else if (i < to + 160) i -= 36;
11+
else if (i < to + 200) i -= 48;
12+
else if (i < to + 300) i -= 80;
13+
else i -= 120;
14+
window.scroll(0, i);
15+
if (i <= to) clearInterval(scrollInterval);
16+
}, 15);
17+
}
18+
19+
function runScroll() {
20+
const smoothScrollFeature =
21+
'scrollBehavior' in document.documentElement.style;
22+
if (!smoothScrollFeature) {
23+
scrollTopSafe(0);
24+
} else {
25+
window.scrollTo({
26+
top: 0,
27+
behavior: 'smooth',
28+
});
29+
}
30+
const content = document.getElementById('content');
31+
const scrollButton = document.getElementById('scroll-to-top');
32+
if (content) {
33+
scrollButton.blur();
34+
content.focus();
35+
}
36+
}
37+
function scrollToTop() {
38+
const element = document.getElementById('scroll-to-top');
39+
if (!element) {
40+
return false;
41+
}
42+
43+
element.addEventListener('click', function () {
44+
runScroll();
45+
});
46+
47+
element.addEventListener('keydown', function (event) {
48+
if (event.key === 'Enter') {
49+
runScroll();
50+
}
51+
});
52+
53+
window.addEventListener('scroll', function () {
54+
const yScrollPos = window.scrollY;
55+
const offset = neveScrollOffset.offset;
56+
57+
if (yScrollPos > offset) {
58+
element.style.visibility = 'visible';
59+
element.style.opacity = '1';
60+
}
61+
if (yScrollPos <= offset) {
62+
element.style.opacity = '0';
63+
element.style.visibility = 'hidden';
64+
}
65+
66+
// Change scroll to top position if there is a sticky add to cart in place.
67+
const stickyAddToCart = document.querySelector(
68+
'.sticky-add-to-cart-bottom'
69+
);
70+
if (stickyAddToCart) {
71+
element.style.bottom = '30px';
72+
73+
// Try to get Neve's sticky footer. If it doesn't exist try to get Elementor's.
74+
let stickyFooter = document.querySelector('.hfg_footer');
75+
if (
76+
!stickyFooter ||
77+
!stickyFooter.classList.contains('has-sticky-rows')
78+
) {
79+
stickyFooter = document.querySelector(
80+
'.elementor-location-footer .elementor-sticky'
81+
);
82+
}
83+
const footerHeight = stickyFooter ? stickyFooter.offsetHeight : 0;
84+
85+
if (
86+
stickyAddToCart.classList.contains('sticky-add-to-cart--active')
87+
) {
88+
element.style.bottom =
89+
stickyAddToCart.offsetHeight + footerHeight + 10 + 'px';
90+
}
91+
}
92+
});
93+
}
94+
95+
window.addEventListener('load', function () {
96+
scrollToTop();
97+
});

inc/admin/dashboard/main.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -667,10 +667,6 @@ private function get_modules() {
667667
'nicename' => __( 'Post types enhancements', 'neve' ),
668668
'description' => __( 'Extend Neve\'s powerful features to custom post types. Create unique layouts for portfolios, testimonials, and more.', 'neve' ),
669669
),
670-
'scroll_to_top' => array(
671-
'nicename' => __( 'Scroll To Top', 'neve' ),
672-
'description' => __( 'Add a customizable scroll-to-top button that appears exactly when needed. Style it to match your brand.', 'neve' ),
673-
),
674670
'performance_features' => array(
675671
'nicename' => __( 'Performance', 'neve' ),
676672
'description' => __( 'Optimize core vitals, enable lazy loading, and minify resources for lightning-fast load times.', 'neve' ),

inc/core/core_loader.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private function define_modules() {
9797
'Views\Content_None',
9898
'Views\Content_404',
9999
'Views\Breadcrumbs',
100+
'Views\Scroll_To_Top',
100101

101102
'Views\Layouts\Layout_Container',
102103
'Views\Layouts\Layout_Sidebar',

inc/core/front_end.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Neve\Core\Settings\Mods;
1717
use Neve\Core\Dynamic_Css;
1818
use Neve\Core\Traits\Theme_Mods;
19+
use Neve\Customizer\Options\Scroll_To_Top;
1920

2021
/**
2122
* Front end handler class.
@@ -86,6 +87,8 @@ public function setup_theme() {
8687
add_filter( 'theme_mod_background_color', '__return_empty_string' );
8788
$this->add_woo_support();
8889
add_filter( 'neve_dynamic_style_output', array( $this, 'css_global_custom_colors' ), PHP_INT_MAX, 2 );
90+
91+
add_filter( 'neve_dynamic_style_output', array( $this, 'css_scroll_to_top' ), 99, 2 );
8992
}
9093

9194
/**
@@ -610,6 +613,89 @@ public function css_global_custom_colors( $current_styles, $context ) {
610613
return $current_styles;
611614
}
612615

616+
/**
617+
* Add module css.
618+
*
619+
* @param string $css Current CSS style.
620+
* @param string $context Current context.
621+
*
622+
* @return string Altered CSS.
623+
*/
624+
public function css_scroll_to_top( $css, $context = 'frontend' ) {
625+
if ( ! Scroll_To_Top::is_enabled() ) {
626+
return $css;
627+
}
628+
629+
if ( $context !== 'frontend' ) {
630+
return $css;
631+
}
632+
633+
$scroll_to_top_css = '.scroll-to-top {' . ( is_rtl() ? 'left: 20px;' : 'right: 20px;' ) . '
634+
border: none;
635+
position: fixed;
636+
bottom: 30px;
637+
display: none;
638+
opacity: 0;
639+
visibility: hidden;
640+
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
641+
align-items: center;
642+
justify-content: center;
643+
z-index: 999;
644+
}
645+
@supports (-webkit-overflow-scrolling: touch) {
646+
.scroll-to-top {
647+
bottom: 74px;
648+
}
649+
}
650+
.scroll-to-top.image {
651+
background-position: center;
652+
}
653+
.scroll-to-top .scroll-to-top-image {
654+
width: 100%;
655+
height: 100%;
656+
}
657+
.scroll-to-top .scroll-to-top-label {
658+
margin: 0;
659+
padding: 5px;
660+
}
661+
.scroll-to-top:hover {
662+
text-decoration: none;
663+
}
664+
.scroll-to-top.scroll-to-top-left {' . ( is_rtl() ? 'right: 20px; left: unset;' : 'left: 20px; right: unset;' ) . '}
665+
.scroll-to-top.scroll-show-mobile {
666+
display: flex;
667+
}
668+
@media (min-width: 960px) {
669+
.scroll-to-top {
670+
display: flex;
671+
}
672+
}';
673+
674+
$scroll_to_top_css .= '.scroll-to-top {
675+
color: var(--color);
676+
padding: var(--padding);
677+
border-radius: var(--borderradius);
678+
background: var(--bgcolor);
679+
}
680+
681+
.scroll-to-top:hover, .scroll-to-top:focus {
682+
color: var(--hovercolor);
683+
background: var(--hoverbgcolor);
684+
}
685+
686+
.scroll-to-top-icon, .scroll-to-top.image .scroll-to-top-image {
687+
width: var(--size);
688+
height: var(--size);
689+
}
690+
691+
.scroll-to-top-image {
692+
background-image: var(--bgimage);
693+
background-size: cover;
694+
}';
695+
696+
return $css . $scroll_to_top_css;
697+
}
698+
613699
/**
614700
* Fix script translations language directory.
615701
*

0 commit comments

Comments
 (0)