Chrome Extension Scroll to Top — Developer Guide

5 min read

Build a Scroll-to-Top Button Extension

A floating button that smoothly scrolls to top when clicked, with customizable appearance and per-site preferences.

Prerequisites: Basic JavaScript and Chrome Extensions knowledge.

What We’re Building

Step 1: Manifest with Content Script

Create manifest.json with content script running on all URLs:

{
  "manifest_version": 3,
  "name": "Scroll to Top Pro",
  "version": "1.0",
  "permissions": ["storage", "activeTab"],
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"],
    "run_at": "document_idle"
  }],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}

Step 2: Floating Button with Shadow DOM

Content script creates an isolated button using Shadow DOM to prevent page style conflicts:

const button = document.createElement('div');
button.id = 'scroll-top-btn';
button.innerHTML = '<button>↑</button>';
const shadow = button.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
  <style>
    #scroll-top-btn { position: fixed; bottom: 20px; right: 20px; z-index: 999999; }
    button { width: 50px; height: 50px; border-radius: 50%; cursor: pointer; }
  </style>
  <button>↑</button>
`;
document.body.appendChild(button);

Step 3: Scroll Listener with Passive Optimization

Show button when scrolled past 300px, hide otherwise. Use passive listener and requestAnimationFrame:

let isVisible = false;
window.addEventListener('scroll', () => {
  requestAnimationFrame(() => {
    const shouldShow = window.scrollY > 300;
    if (shouldShow !== isVisible) {
      isVisible = shouldShow;
      button.style.display = shouldShow ? 'block' : 'none';
    }
  });
}, { passive: true });

Step 4: Smooth Scroll Action

On button click, smoothly scroll to top:

button.addEventListener('click', () => {
  window.scrollTo({ top: 0, behavior: 'smooth' });
});

Step 5: Popup with Position Options

Add position controls (left/right, offset) in popup.html:

<select id="position">
  <option value="right">Right</option>
  <option value="left">Left</option>
</select>
<input type="number" id="offset" value="20" placeholder="Offset (px)">

Step 6: Style Customization

Allow customization: color, size, opacity, shape (circle/square/pill):

// In content.js, read from storage and apply styles
chrome.storage.sync.get(['btnColor', 'btnSize', 'btnShape'], ( prefs ) => {
  const style = shadow.querySelector('style');
  style.textContent += `
    button {
      background: ${prefs.btnColor || '#333'};
      width: ${prefs.btnSize || 50}px;
      height: ${prefs.btnSize || 50}px;
      border-radius: ${prefs.btnShape === 'pill' ? '25px' : 
                       prefs.btnShape === 'square' ? '4px' : '50%'};
      opacity: ${prefs.btnOpacity || 0.8};
    }
  `;
});

Step 7: Per-Site or Global Preferences

Store settings per-site using domain keys or globally:

// Save per-site
const domain = window.location.hostname;
chrome.storage.local.set({ [`settings_${domain}`]: prefs });

// Save global
chrome.storage.sync.set({ globalSettings: prefs });

Step 8: Circular Progress Ring

Add SVG progress ring around button showing scroll percentage:

const progress = Math.min(scrollY / (document.body.scrollHeight - window.innerHeight), 1);
shadow.querySelector('svg circle').style.strokeDashoffset = 
  2 * Math.PI * 40 * (1 - progress);

Performance Best Practices

Handling Custom Scroll Containers

For sites with custom scroll containers, detect and handle:

const scrollable = document.querySelector('.custom-scroll-container');
if (scrollable) {
  scrollable.addEventListener('scroll', handleScroll);
}

Excluding Specific Sites

Add to manifest’s content_scripts.matches or use runtime.onInstalled:

"exclude_matches": ["*://*.facebook.com/*", "*://*.twitter.com/*"]

Cross-References

What You Learned

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article