Build a Viewport Size Checker Chrome Extension: Complete Developer's Guide

23 min read

Build a Viewport Size Checker Chrome Extension: Complete Developer's Guide

Build a Viewport Size Checker Chrome Extension: Complete Developer’s Guide

In the world of modern web development, understanding how your website renders across different viewport sizes is crucial. Whether you’re building responsive layouts, debugging CSS issues, or testing breakpoints, having a reliable viewport checker extension in your browser can save countless hours of manual testing. In this comprehensive guide, we’ll walk you through building a fully functional Viewport Size Checker Chrome Extension that displays real-time viewport dimensions, device information, and provides useful tools for responsive design development.

The demand for responsive web design has never been higher. With users accessing websites from an ever-growing variety of devices — from large desktop monitors to small mobile phones — developers need robust tools to ensure their designs work seamlessly across all screen sizes. A custom viewport checker extension gives you instant access to viewport information without relying on browser DevTools, making it an essential addition to any developer’s toolkit.

Why Build a Viewport Checker Extension?

Before diving into the code, let’s explore why a viewport checker extension is valuable for web developers. Traditional methods of checking viewport sizes involve opening Chrome DevTools, toggling the device toolbar, and manually reading dimensions. While DevTools is powerful, having a dedicated extension provides several advantages:

Instant Access: A viewport checker extension displays dimensions directly in your browser toolbar, accessible with a single click. No need to open DevTools panels or switch contexts.

Real-Time Monitoring: The best viewport checker extensions update in real-time as you resize windows or navigate between pages, providing immediate feedback on how viewport sizes change.

Custom Features: Building your own extension allows you to add features tailored to your specific workflow, whether that’s breakpoint indicators, device frame overlays, or export functionality.

Learning Opportunity: Creating a Chrome extension is an excellent way to learn the extension development workflow, manifest versioning, and browser API usage — skills that transfer to many other extension projects.

Project Architecture and File Structure

Our viewport checker extension will consist of several key components working together to provide a seamless user experience. Let’s examine the file structure we’ll create:

viewport-checker/
├── manifest.json          # Extension configuration and permissions
├── popup.html             # Extension popup UI
├── popup.js               # Popup logic and viewport calculations
├── popup.css              # Styling for the popup
├── content.js             # Content script for page interaction
├── background.js          # Background service worker
└── icons/                 # Extension icons
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

Each file serves a specific purpose in our extension architecture. The manifest.json defines our extension’s capabilities and permissions, while the popup files handle the user interface. Content scripts allow us to interact with web pages, and the background service worker manages communication between components.

Creating the Manifest File

Every Chrome extension begins with a manifest.json file that declares the extension’s configuration, permissions, and capabilities. For our viewport checker, we’ll use Manifest V3, which is the current standard and provides improved security and performance.

{
  "manifest_version": 3,
  "name": "Viewport Size Checker",
  "version": "1.0.0",
  "description": "Display real-time viewport dimensions and device information for responsive design testing",
  "permissions": [
    "activeTab",
    "storage"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ],
  "background": {
    "service_worker": "background.js"
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

This manifest declares the essential permissions our extension needs. We request “activeTab” permission to access information about the current tab, and “storage” permission to save user preferences. The “action” key defines our popup, and “content_scripts” allows us to inject JavaScript into web pages to measure viewport dimensions.

Building the Content Script

The content script is the heart of our viewport measurement logic. It runs in the context of web pages and can access the DOM to retrieve accurate viewport information. Let’s create a robust content script:

// content.js - Runs in the context of web pages
(function() {
  // Store previous dimensions to detect changes
  let previousDimensions = {
    width: window.innerWidth,
    height: window.innerHeight
  };

  // Function to get comprehensive viewport information
  function getViewportInfo() {
    const info = {
      // Core viewport dimensions
      viewportWidth: window.innerWidth,
      viewportHeight: window.innerHeight,
      
      // Outer dimensions (including toolbars)
      outerWidth: window.outerWidth,
      outerHeight: window.outerHeight,
      
      // Document dimensions
      documentWidth: document.documentElement.scrollWidth,
      documentHeight: document.documentElement.scrollHeight,
      
      // Screen dimensions
      screenWidth: screen.width,
      screenHeight: screen.height,
      availScreenWidth: screen.availWidth,
      availScreenHeight: screen.availHeight,
      
      // Device pixel ratio for high-DPI displays
      devicePixelRatio: window.devicePixelRatio,
      
      // Orientation
      orientation: screen.orientation ? screen.orientation.type : 'unknown',
      
      // Color depth
      colorDepth: screen.colorDepth,
      
      // Breakpoint detection
      breakpoint: getBreakpoint(window.innerWidth),
      
      // Pixel ratio adjusted dimensions
      deviceWidth: window.innerWidth * window.devicePixelRatio,
      deviceHeight: window.innerHeight * window.devicePixelRatio
    };
    
    return info;
  }

  // Determine current responsive breakpoint
  function getBreakpoint(width) {
    if (width < 576) return 'xs';      // Extra small
    if (width < 768) return 'sm';       // Small
    if (width < 992) return 'md';      // Medium
    if (width < 1200) return 'lg';     // Large
    if (width < 1400) return 'xl';     // Extra large
    return 'xxl';                       // Extra extra large
  }

  // Listen for viewport changes
  function setupResizeListener() {
    let resizeTimeout;
    
    window.addEventListener('resize', function() {
      clearTimeout(resizeTimeout);
      resizeTimeout = setTimeout(function() {
        const currentDimensions = {
          width: window.innerWidth,
          height: window.innerHeight
        };
        
        // Only notify if dimensions actually changed
        if (currentDimensions.width !== previousDimensions.width ||
            currentDimensions.height !== previousDimensions.height) {
          previousDimensions = currentDimensions;
          notifyBackgroundOfChange();
        }
      }, 100); // Debounce resize events
    });
  }

  // Notify background script of viewport changes
  function notifyBackgroundOfChange() {
    const viewportInfo = getViewportInfo();
    chrome.runtime.sendMessage({
      type: 'VIEWPORT_CHANGED',
      data: viewportInfo
    });
  }

  // Initialize the content script
  function init() {
    setupResizeListener();
    
    // Send initial viewport info to background
    notifyBackgroundOfChange();
    
    // Listen for messages from popup or background
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      if (message.type === 'GET_VIEWPORT_INFO') {
        sendResponse(getViewportInfo());
      }
      return true;
    });
  }

  // Start the content script
  init();
})();

This content script provides comprehensive viewport information including not just the basic innerWidth and innerHeight, but also screen dimensions, device pixel ratio for retina displays, breakpoint detection based on common responsive frameworks, and orientation information. The resize listener includes debouncing to prevent excessive updates during window dragging.

Creating the Background Service Worker

The background service worker acts as a central hub for communication between our extension’s components. It manages state and facilitates message passing:

// background.js - Service worker for extension lifecycle management
let cachedViewportInfo = null;

// Listen for viewport updates from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'VIEWPORT_CHANGED') {
    cachedViewportInfo = message.data;
    
    // Update badge with current width
    chrome.action.setBadgeText({
      text: message.data.viewportWidth.toString()
    });
    
    // Set badge background color
    chrome.action.setBadgeBackgroundColor({
      color: '#4285f4'
    });
  }
  
  if (message.type === 'GET_CACHED_VIEWPORT_INFO') {
    sendResponse(cachedViewportInfo);
  }
  
  return true;
});

// Handle extension installation
chrome.runtime.onInstalled.addListener((details) => {
  console.log('Viewport Checker installed:', details.reason);
});

// Clean up when extension is updated
chrome.runtime.onUpdateAvailable.addListener(() => {
  console.log('Viewport Checker update available');
});

The background service worker caches viewport information for quick access and updates the extension badge to show the current viewport width at a glance. This provides immediate feedback even before clicking on the extension.

Building the Popup Interface

The popup provides the main user interface for our extension. Let’s create an attractive and functional popup:

<!-- popup.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Viewport Checker</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="popup-container">
    <header class="popup-header">
      <h1>Viewport Checker</h1>
      <span class="badge-info">Live</span>
    </header>
    
    <main class="popup-content">
      <section class="dimensions-display">
        <div class="dimension-item primary">
          <span class="dimension-label">Viewport</span>
          <span class="dimension-value" id="viewport-dimensions">-- × --</span>
        </div>
        
        <div class="dimension-item">
          <span class="dimension-label">Screen</span>
          <span class="dimension-value" id="screen-dimensions">-- × --</span>
        </div>
        
        <div class="dimension-item">
          <span class="dimension-label">Document</span>
          <span class="dimension-value" id="document-dimensions">-- × --</span>
        </div>
      </section>
      
      <section class="device-info">
        <div class="info-grid">
          <div class="info-item">
            <span class="info-label">Breakpoint</span>
            <span class="info-value" id="breakpoint">--</span>
          </div>
          <div class="info-item">
            <span class="info-label">Pixel Ratio</span>
            <span class="info-value" id="pixel-ratio">--</span>
          </div>
          <div class="info-item">
            <span class="info-label">Orientation</span>
            <span class="info-value" id="orientation">--</span>
          </div>
          <div class="info-item">
            <span class="info-label">Color Depth</span>
            <span class="info-value" id="color-depth">--</span>
          </div>
        </div>
      </section>
      
      <section class="visual-representation">
        <div class="viewport-visual" id="viewport-visual">
          <div class="visual-label">Visual</div>
        </div>
      </section>
    </main>
    
    <footer class="popup-footer">
      <button id="copy-btn" class="action-btn">
        <span class="btn-icon">📋</span> Copy Info
      </button>
      <button id="refresh-btn" class="action-btn secondary">
        <span class="btn-icon">🔄</span> Refresh
      </button>
    </footer>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>

The popup presents information in a clear, organized layout with primary dimensions prominently displayed, device information in a grid format, and a visual representation of the viewport relative to the screen.

Styling the Popup

Let’s create attractive CSS styling for our popup:

/* popup.css */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  width: 320px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: #333;
}

.popup-container {
  padding: 16px;
}

.popup-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}

.popup-header h1 {
  font-size: 18px;
  font-weight: 600;
  color: white;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.badge-info {
  background: #10b981;
  color: white;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 500;
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

.popup-content {
  background: white;
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.dimensions-display {
  margin-bottom: 16px;
}

.dimension-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid #f0f0f0;
}

.dimension-item:last-child {
  border-bottom: none;
}

.dimension-item.primary {
  background: linear-gradient(90deg, #f8f9fa 0%, #e9ecef 100%);
  margin: -16px -16px 16px -16px;
  padding: 16px;
  border-radius: 12px 12px 0 0;
  border-bottom: none;
}

.dimension-label {
  color: #6c757d;
  font-size: 13px;
}

.dimension-value {
  font-weight: 600;
  font-size: 16px;
  color: #212529;
  font-family: 'Monaco', 'Menlo', monospace;
}

.dimension-item.primary .dimension-value {
  font-size: 20px;
  color: #667eea;
}

.device-info {
  margin-bottom: 16px;
}

.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.info-item {
  background: #f8f9fa;
  padding: 10px;
  border-radius: 8px;
  text-align: center;
}

.info-label {
  display: block;
  font-size: 11px;
  color: #6c757d;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}

.info-value {
  display: block;
  font-weight: 600;
  color: #212529;
  font-size: 14px;
}

.visual-representation {
  background: #f8f9fa;
  border-radius: 8px;
  padding: 16px;
  text-align: center;
}

.viewport-visual {
  width: 100%;
  height: 60px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 4px;
  position: relative;
  margin: 0 auto;
  max-width: 200px;
}

.visual-label {
  position: absolute;
  bottom: 4px;
  right: 8px;
  font-size: 10px;
  color: white;
  opacity: 0.8;
}

.popup-footer {
  display: flex;
  gap: 8px;
  margin-top: 16px;
}

.action-btn {
  flex: 1;
  padding: 10px 16px;
  border: none;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.action-btn.primary {
  background: white;
  color: #667eea;
}

.action-btn.primary:hover {
  background: #f0f0f0;
  transform: translateY(-1px);
}

.action-btn.secondary {
  background: rgba(255, 255, 255, 0.2);
  color: white;
}

.action-btn.secondary:hover {
  background: rgba(255, 255, 255, 0.3);
  transform: translateY(-1px);
}

.btn-icon {
  font-size: 14px;
}

The styling creates a modern, visually appealing popup with a gradient header, organized information sections, and interactive buttons with hover effects.

Implementing Popup Logic

Finally, let’s create the JavaScript for the popup:

// popup.js - Popup interface logic
document.addEventListener('DOMContentLoaded', function() {
  // Get the active tab and request viewport information
  chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
    if (tabs[0]) {
      // Send message to content script to get viewport info
      chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_VIEWPORT_INFO' }, function(response) {
        if (response) {
          updatePopup(response);
        } else {
          // Fallback: try to get from background cache
          chrome.runtime.sendMessage({ type: 'GET_CACHED_VIEWPORT_INFO' }, function(cached) {
            if (cached) {
              updatePopup(cached);
            } else {
              showError();
            }
          });
        }
      });
    }
  });

  // Update popup UI with viewport information
  function updatePopup(info) {
    // Update dimensions
    document.getElementById('viewport-dimensions').textContent = 
      `${info.viewportWidth} × ${info.viewportHeight}`;
    document.getElementById('screen-dimensions').textContent = 
      `${info.screenWidth} × ${info.screenHeight}`;
    document.getElementById('document-dimensions').textContent = 
      `${info.documentWidth} × ${info.documentHeight}`;
    
    // Update device info
    document.getElementById('breakpoint').textContent = info.breakpoint.toUpperCase();
    document.getElementById('pixel-ratio').textContent = `${info.devicePixelRatio}x`;
    document.getElementById('orientation').textContent = info.orientation.split('-')[0];
    document.getElementById('color-depth').textContent = `${info.colorDepth}-bit`;
    
    // Update visual representation
    updateVisualRepresentation(info);
  }

  // Update the visual viewport representation
  function updateVisualRepresentation(info) {
    const visual = document.getElementById('viewport-visual');
    const aspectRatio = info.viewportWidth / info.viewportHeight;
    
    // Calculate visual dimensions (scaled)
    const maxWidth = 160;
    const maxHeight = 60;
    
    let visualWidth, visualHeight;
    if (aspectRatio > maxWidth / maxHeight) {
      visualWidth = maxWidth;
      visualHeight = maxWidth / aspectRatio;
    } else {
      visualHeight = maxHeight;
      visualWidth = maxHeight * aspectRatio;
    }
    
    visual.style.width = `${visualWidth}px`;
    visual.style.height = `${visualHeight}px`;
    visual.style.margin = '0 auto';
  }

  // Show error state
  function showError() {
    document.getElementById('viewport-dimensions').textContent = 'Error';
    console.error('Could not retrieve viewport information');
  }

  // Copy button functionality
  document.getElementById('copy-btn').addEventListener('click', function() {
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
      chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_VIEWPORT_INFO' }, function(info) {
        if (info) {
          const text = `Viewport: ${info.viewportWidth}x${info.viewportHeight}
Screen: ${info.screenWidth}x${info.screenHeight}
Breakpoint: ${info.breakpoint}
Device Pixel Ratio: ${info.devicePixelRatio}
Orientation: ${info.orientation}`;
          
          navigator.clipboard.writeText(text).then(function() {
            const btn = document.getElementById('copy-btn');
            btn.innerHTML = '<span class="btn-icon">✓</span> Copied!';
            setTimeout(function() {
              btn.innerHTML = '<span class="btn-icon">📋</span> Copy Info';
            }, 2000);
          });
        }
      });
    });
  });

  // Refresh button functionality
  document.getElementById('refresh-btn').addEventListener('click', function() {
    chrome.tabs.reload();
  });
});

The popup JavaScript handles retrieving viewport information, updating the UI dynamically, copying information to the clipboard, and refreshing the current tab.

Testing Your Extension

Now that we’ve created all the necessary files, let’s test our extension:

  1. Create the icons directory and add placeholder icons (or use any PNG images named icon16.png, icon48.png, and icon128.png)

  2. Open Chrome and navigate to chrome://extensions/

  3. Enable Developer mode using the toggle in the top right corner

  4. Click “Load unpacked” and select your extension directory

  5. Test the extension by visiting any website and clicking the extension icon

You should see the popup displaying real-time viewport dimensions, device information, and breakpoint detection. The extension badge will show the current viewport width.

Enhancements and Future Improvements

This viewport checker extension provides a solid foundation that you can extend with additional features:

Breakpoint Overlays: Add the ability to visually overlay common breakpoint markers on web pages to see exactly where your design will break.

History Tracking: Record viewport sizes over time as you navigate, creating a log of all the viewports you’ve tested.

Screenshot Capture: Add functionality to capture screenshots at specific viewport sizes for documentation.

Custom Breakpoints: Allow users to define their own breakpoint thresholds based on their specific design requirements.

Export Functionality: Export viewport data in various formats (JSON, CSV) for testing reports.

Integration with Design Tools: Export dimensions directly to Figma, Sketch, or other design tools.

Conclusion

Building a viewport checker extension is an excellent project that combines practical utility with valuable learning opportunities. You’ve learned how to create a Manifest V3 extension, implement content scripts for DOM access, build background service workers for state management, and design attractive popup interfaces.

This extension solves a real problem for web developers: quickly accessing viewport information without leaving the browser context. The real-time updates, comprehensive device information, and visual representation make it an invaluable tool for responsive design development.

As you continue developing Chrome extensions, remember that the techniques learned here — message passing between components, content script injection, and popup interface design — apply to virtually any extension project you undertake. The Chrome Web Store offers vast opportunities for developers to share useful tools with millions of users.

Start building your viewport checker today, and customize it to match your specific development workflow. The foundation is solid, and the possibilities for enhancement are virtually unlimited.

No previous article
No next article