DEV Community

Cover image for Developing a Responsive Web Tester: Build Your Own Device Preview Utility
Learn Computer Academy
Learn Computer Academy

Posted on

1

Developing a Responsive Web Tester: Build Your Own Device Preview Utility

In the modern web development landscape, creating sites that look great on every device isn't just good practice—it's essential. Today, I'm excited to share a powerful tool I've developed called "Responsive Tester Pro" that makes cross-device testing incredibly simple.

Try it yourself: Check out the Responsive Tester Pro and see how any website renders across different screen sizes!

The Problem With Responsive Testing 🤔

As developers, we've all been there—frantically resizing browser windows or reaching for every device we own to check if our layouts work. This approach is:

  • Time-consuming (constantly switching between physical devices)
  • Inconsistent (missing common breakpoints)
  • Impractical (who owns every possible device size?)

Enter Responsive Tester Pro 🚀

I built this tool to solve these exact problems. The application lets you:

  • Test any website across predefined device dimensions
  • Create custom screen sizes for specific testing needs
  • Toggle between portrait and landscape orientations
  • View websites in dark mode to test color schemes
  • Reload frames and view sites in full screen

The beauty lies in its simplicity—just enter a URL, select a device size, and instantly see how that site renders.

How It Works: The Technical Breakdown

Behind the scenes, the application uses three core technologies that work together seamlessly:

The Structural Foundation (HTML)

The HTML creates an intuitive interface with several strategic elements:

  • A clean, accessible header that clearly communicates the tool's purpose
  • Feature toggles positioned for easy access
  • A prominent URL input field with validation
  • Device presets that match common screen dimensions
  • A preview iframe that safely renders websites without security risks

The iframe is particularly important—it provides a sandboxed environment where external websites can load without interacting with our application's code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Tester Pro</title>
    <link rel="stylesheet" href="styles.css">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
</head>
<body>
    <div class="container">
        <header>
            <h1>Responsive Tester Pro</h1>
            <p>Preview your website across devices</p>
        </header>

        <div class="features">
            <button class="btn feature-btn" id="reloadBtn">Reload</button>
            <button class="btn feature-btn" id="fullScreenBtn">Full Screen</button>
            <button class="btn feature-btn" id="darkModeBtn">Dark Mode</button>
            <button class="btn feature-btn" id="orientationBtn">Landscape</button>
        </div>

        <div class="controls">
            <div class="url-input">
                <input type="url" id="urlInput" placeholder="Enter website URL (e.g., https://learncomputer.in)">
                <button id="loadBtn" class="btn primary">Test Now</button>
            </div>

            <div class="presets">
                <button class="preset-btn" data-width="375" data-height="667">Mobile</button>
                <button class="preset-btn" data-width="768" data-height="1024">Tablet</button>
                <button class="preset-btn" data-width="1366" data-height="768">Laptop</button>
                <button class="preset-btn" data-width="1920" data-height="1080">Desktop</button>
                <button class="preset-btn custom" id="customBtn">Custom</button>
            </div>

            <div class="custom-sizes" id="customSizes" style="display: none;">
                <input type="number" id="customWidth" placeholder="Width (px)" min="100">
                <input type="number" id="customHeight" placeholder="Height (px)" min="100">
                <button class="btn secondary" id="applyCustom">Apply</button>
            </div>
        </div>

        <div class="preview-container">
            <div class="device-frame" id="deviceFrame">
                <div class="device-header">
                    <div class="device-info">
                        <span id="deviceSize">375x667</span>
                        <button class="rotate-btn" id="rotateBtn"></button>
                    </div>
                </div>
                <div class="loading-overlay" id="loadingOverlay">
                    <div class="loader"></div>
                </div>
                <iframe id="preview" sandbox="allow-same-origin allow-scripts allow-forms"></iframe>
            </div>
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The Visual Experience (CSS)

The styling transforms the basic structure into an intuitive, attractive interface through:

  • Neumorphic design elements with subtle shadows creating depth
  • A responsive layout that adapts to the user's own screen size
  • Smooth transitions between states for a polished feel
  • A thoughtfully designed dark mode that protects eyes during late-night coding sessions
  • Visual feedback systems that communicate the application state

The CSS employs modern techniques like flexbox for layout management and CSS variables for theming consistency.


* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Inter', sans-serif;
    background: #f0f2f5;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px;
    transition: background 0.3s;
}

body.dark-mode {
    background: #1a1a2e;
}

.container {
    max-width: 1200px;
    width: 100%;
    background: #ffffff;
    border-radius: 20px;
    padding: 30px;
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
    transition: background 0.3s;
}

.dark-mode .container {
    background: #16213e;
}

header {
    text-align: center;
    margin-bottom: 30px;
}

h1 {
    color: #1a1a2e;
    font-size: 2.2em;
    font-weight: 700;
}

.dark-mode h1 {
    color: #e0e0e0;
}

p {
    color: #666;
    font-size: 1.1em;
}

.dark-mode p {
    color: #a0a0a0;
}

.features {
    display: flex;
    justify-content: center;
    gap: 15px;
    margin-bottom: 30px;
}

.btn {
    padding: 10px 20px;
    border: none;
    border-radius: 12px;
    cursor: pointer;
    font-weight: 600;
    background: #ffffff;
    box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1),
                -5px -5px 10px rgba(255, 255, 255, 0.8);
    transition: all 0.3s;
}

.dark-mode .btn {
    background: #16213e;
    color: #e0e0e0;
    box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2),
                -5px -5px 10px rgba(40, 50, 80, 0.4);
}

.btn:hover {
    box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.1),
                inset -2px -2px 5px rgba(255, 255, 255, 0.7);
}

.primary {
    background: #4e4feb;
    color: white;
}

.secondary {
    background: #00cc99;
    color: white;
}

.url-input {
    display: flex;
    gap: 15px;
    margin-bottom: 25px;
}

input[type="url"],
input[type="number"] {
    flex: 1;
    padding: 12px 15px;
    border: none;
    border-radius: 12px;
    font-size: 1em;
    background: #f0f2f5;
    box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.1),
                inset -2px -2px 5px rgba(255, 255, 255, 0.7);
    transition: all 0.3s;
}

.dark-mode input[type="url"],
.dark-mode input[type="number"] {
    background: #0f1626;
    color: #e0e0e0;
}

input:focus {
    outline: none;
    box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.15),
                inset -1px -1px 3px rgba(255, 255, 255, 0.9);
}

.presets {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 20px;
    justify-content: center;
}

.preset-btn {
    padding: 10px 18px;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    background: #f0f2f5;
    box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1),
                -3px -3px 6px rgba(255, 255, 255, 0.8);
    transition: all 0.3s;
}

.dark-mode .preset-btn {
    background: #0f1626;
    color: #e0e0e0;
}

.preset-btn:hover,
.preset-btn.active {
    background: #4e4feb;
    color: white;
    box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.2);
}

.custom-sizes {
    display: flex;
    gap: 12px;
}

.preview-container {
    display: flex;
    justify-content: center;
}

.device-frame {
    background: #ffffff;
    border-radius: 15px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
    position: relative;
    transition: all 0.3s;
}

.dark-mode .device-frame {
    background: #16213e;
}

.device-header {
    background: #4e4feb;
    padding: 10px;
    color: white;
    display: flex;
    justify-content: center;
    border-radius: 15px 15px 0 0;
}

.device-info {
    display: flex;
    align-items: center;
    gap: 15px;
}

.rotate-btn {
    background: none;
    border: none;
    color: white;
    font-size: 20px;
    cursor: pointer;
    transition: transform 0.3s;
}

.rotate-btn:hover {
    transform: rotate(90deg);
}

.loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s;
    border-radius: 15px;
}

.loading-overlay.active {
    opacity: 1;
    visibility: visible;
}

.loader {
    width: 40px;
    height: 40px;
    border: 4px solid #fff;
    border-bottom-color: #4e4feb;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

iframe {
    border: none;
    width: 375px;
    height: 667px;
    border-radius: 0 0 15px 15px;
    transition: all 0.3s;
}

@media (max-width: 768px) {
    .container {
        padding: 20px;
    }

    .url-input {
        flex-direction: column;
    }

    .features {
        flex-wrap: wrap;
    }
}
Enter fullscreen mode Exit fullscreen mode

The Intelligent Behavior (JavaScript)

The JavaScript brings everything to life by:

  1. Managing URL loading: Validating input, handling protocols, and providing error feedback
  2. Controlling device dimensions: Translating preset selections into precise pixel measurements
  3. Handling orientation changes: Swapping width and height values while maintaining context
  4. Providing utility features: Implementing dark mode, full-screen viewing, and reload functionality
  5. Creating visual feedback: Showing loading states during transitions between websites

One particularly interesting function is the orientation handling, which doesn't just swap width and height values but also updates the UI to reflect the current orientation state.


document.addEventListener('DOMContentLoaded', () => {
    const urlInput = document.getElementById('urlInput');
    const loadBtn = document.getElementById('loadBtn');
    const preview = document.getElementById('preview');
    const deviceFrame = document.getElementById('deviceFrame');
    const deviceSize = document.getElementById('deviceSize');
    const rotateBtn = document.getElementById('rotateBtn');
    const presetButtons = document.querySelectorAll('.preset-btn');
    const customBtn = document.getElementById('customBtn');
    const customSizes = document.getElementById('customSizes');
    const customWidth = document.getElementById('customWidth');
    const customHeight = document.getElementById('customHeight');
    const applyCustom = document.getElementById('applyCustom');
    const reloadBtn = document.getElementById('reloadBtn');
    const fullScreenBtn = document.getElementById('fullScreenBtn');
    const darkModeBtn = document.getElementById('darkModeBtn');
    const orientationBtn = document.getElementById('orientationBtn');
    const loadingOverlay = document.getElementById('loadingOverlay');

    let currentWidth = 375;
    let currentHeight = 667;
    let isRotated = false;


    loadBtn.addEventListener('click', loadWebsite);
    urlInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') loadWebsite();
    });

    function loadWebsite() {
        const url = urlInput.value.trim();
        if (url) {
            const finalUrl = url.startsWith('http') ? url : `https://${url}`;
            loadingOverlay.classList.add('active');
            preview.src = finalUrl;
        }
    }


    presetButtons.forEach(button => {
        button.addEventListener('click', () => {
            if (button.classList.contains('custom')) {
                customSizes.style.display = 'flex';
                return;
            }

            currentWidth = parseInt(button.dataset.width);
            currentHeight = parseInt(button.dataset.height);
            updatePreview();

            presetButtons.forEach(btn => btn.classList.remove('active'));
            button.classList.add('active');
        });
    });


    applyCustom.addEventListener('click', () => {
        const width = parseInt(customWidth.value);
        const height = parseInt(customHeight.value);

        if (width >= 100 && height >= 100) {
            currentWidth = width;
            currentHeight = height;
            updatePreview();
            customSizes.style.display = 'none';
        }
    });


    rotateBtn.addEventListener('click', () => {
        isRotated = !isRotated;
        updatePreview();
    });


    function updatePreview() {
        const width = isRotated ? currentHeight : currentWidth;
        const height = isRotated ? currentWidth : currentHeight;

        preview.style.width = `${width}px`;
        preview.style.height = `${height}px`;
        deviceSize.textContent = `${width}x${height}`;

        orientationBtn.textContent = isRotated ? 'Portrait' : 'Landscape';
    }


    reloadBtn.addEventListener('click', () => {
        loadingOverlay.classList.add('active');
        preview.contentWindow.location.reload();
    });

    fullScreenBtn.addEventListener('click', () => {
        if (deviceFrame.requestFullscreen) {
            deviceFrame.requestFullscreen();
        }
    });

    darkModeBtn.addEventListener('click', () => {
        document.body.classList.toggle('dark-mode');
        darkModeBtn.textContent = document.body.classList.contains('dark-mode') 
            ? 'Light Mode' 
            : 'Dark Mode';
    });

    orientationBtn.addEventListener('click', () => {
        isRotated = !isRotated;
        updatePreview();
    });

    // Handle iframe load events
    preview.addEventListener('load', () => {
        loadingOverlay.classList.remove('active');
    });

    preview.addEventListener('error', () => {
        loadingOverlay.classList.remove('active');
        alert('Failed to load website. Please check the URL and try again.');
    });

    // Initial setup
    updatePreview();
});
Enter fullscreen mode Exit fullscreen mode

Learning From The Code

Building this tool taught me several valuable lessons about web development:

1. User Experience Considerations

Notice how the loading overlay provides immediate feedback when changing URLs. This prevents the jarring experience of seeing a blank iframe while content loads. Small details like this make applications feel professional and thoughtful.

2. Progressive Enhancement

The tool works at its core even if JavaScript features like full-screen mode aren't available. This demonstrates the principle of progressive enhancement—start with a functional base and layer on enhancements.

3. Responsive Design Itself

Ironically, a tool for testing responsive design must itself be responsive! The media queries ensure the interface remains usable even on smaller screens.

4. Security Considerations

The iframe uses the sandbox attribute to prevent potentially malicious websites from accessing our application. Always consider security implications when loading external content.

Taking It Further

If you're inspired to build on this project, consider these enhancements:

  1. Adding device-specific frames (like phone bezels) for more realistic previews
  2. Implementing screenshot functionality for documentation
  3. Creating shareable test URLs so teammates can view the same test
  4. Adding network throttling to simulate various connection speeds
  5. Incorporating accessibility testing tools alongside visual testing

Why This Matters

Responsive design isn't just about aesthetics—it's about inclusion. When websites work well across devices, we create more accessible digital experiences for everyone, regardless of their technology access.

This simple tool embodies an important principle: testing should be easy enough that developers actually do it. When testing becomes frictionless, the web becomes more accessible for everyone.

Have you created your own development tools? What problems did they solve? I'd love to hear about your experiences in the comments below!

Tiugo image

Fast, Lean, and Fully Extensible

CKEditor 5 is built for developers who value flexibility and speed. Pick the features that matter, drop the ones that don’t and enjoy a high-performance WYSIWYG that fits into your workflow

Start now

Top comments (0)

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay