JavaScript Essentials

Keyboard & Mouse Events

45 min Lesson 27 of 60

Introduction to User Input Events

Every interactive web application relies on responding to user input. Whether a user is typing into a search bar, clicking a button, hovering over a menu, or dragging an element across the screen, JavaScript provides a rich set of events to detect and respond to these actions. In this lesson, you will master keyboard events and mouse events -- the two most fundamental categories of user input. You will learn how to listen for specific keys, detect modifier combinations, track mouse position, handle various click types, and build real-world features like keyboard shortcuts, drawing canvases, and custom context menus. By the end, you will also be introduced to pointer events and touch events that unify input handling across devices.

Keyboard Events Overview

JavaScript provides three keyboard events that fire in a specific order when a user presses a key:

  • keydown -- Fires when a key is pressed down. This is the most commonly used keyboard event. It fires repeatedly if the key is held down.
  • keypress -- Fires after keydown for keys that produce a character value. This event is deprecated and should not be used in new code. It does not fire for non-character keys like Shift, Ctrl, or Arrow keys.
  • keyup -- Fires when a key is released. It fires once per key release, regardless of how long the key was held.

The firing order is: keydown first, then keypress (if applicable), then keyup when the key is released. For modern development, you should use keydown and keyup exclusively and avoid keypress entirely.

Example: Listening to All Three Keyboard Events

const input = document.getElementById('search-input');

input.addEventListener('keydown', function(event) {
    console.log('keydown:', event.key, 'code:', event.code);
});

// Deprecated -- avoid in new code
input.addEventListener('keypress', function(event) {
    console.log('keypress:', event.key);
});

input.addEventListener('keyup', function(event) {
    console.log('keyup:', event.key, 'code:', event.code);
});
Important: The keypress event is officially deprecated in the DOM specification. It behaves inconsistently across browsers and does not fire for many keys like Enter, Escape, Backspace, and arrow keys in some browsers. Always use keydown or keyup instead.

KeyboardEvent Properties

The KeyboardEvent object provides several important properties to identify which key was pressed and what modifier keys were active at the time:

The key Property

The event.key property returns the value of the key that was pressed. For printable characters, it returns the character itself (like "a", "A", "1", "!"). For special keys, it returns a descriptive string like "Enter", "Escape", "ArrowUp", "Shift", "Control", or "Tab". The value of key takes into account modifier keys -- pressing Shift + a gives "A", not "a".

The code Property

The event.code property returns a string representing the physical key on the keyboard, regardless of the current keyboard layout or modifier state. For example, pressing the key labeled "A" on a QWERTY keyboard always returns "KeyA" whether or not Shift is held. This is useful for games and keyboard shortcuts where you want the physical key position rather than the character it produces.

Example: Difference Between key and code

document.addEventListener('keydown', function(event) {
    console.log('key:', event.key);   // "a" or "A" depending on Shift
    console.log('code:', event.code); // Always "KeyA" for the A key

    // Physical key codes for special keys
    // "Enter"      -> code: "Enter"
    // "Space"      -> code: "Space"
    // "ArrowUp"    -> code: "ArrowUp"
    // "Digit1"     -> code: "Digit1"
    // "ShiftLeft"  -> code: "ShiftLeft"
    // "ShiftRight" -> code: "ShiftRight"
});
Note: The older properties event.keyCode and event.which are deprecated. They return numeric codes that vary across browsers and keyboard layouts. Always use event.key or event.code in modern JavaScript.

Modifier Key Properties

The KeyboardEvent object includes boolean properties to check whether modifier keys were held down during the event:

  • event.ctrlKey -- true if the Ctrl key was held down.
  • event.shiftKey -- true if the Shift key was held down.
  • event.altKey -- true if the Alt key (or Option on Mac) was held down.
  • event.metaKey -- true if the Meta key was held down (Windows key on PC, Command key on Mac).

Example: Detecting Modifier Keys

document.addEventListener('keydown', function(event) {
    if (event.ctrlKey) {
        console.log('Ctrl is held down');
    }
    if (event.shiftKey) {
        console.log('Shift is held down');
    }
    if (event.altKey) {
        console.log('Alt/Option is held down');
    }
    if (event.metaKey) {
        console.log('Meta/Command is held down');
    }
    // Combined example: Ctrl + Shift + S
    if (event.ctrlKey && event.shiftKey && event.key === 'S') {
        console.log('Ctrl+Shift+S was pressed');
    }
});

The repeat Property

The event.repeat property is a boolean that is true when the event is firing because the key is being held down (auto-repeat). This is useful when you want to distinguish between an initial key press and a held key.

Example: Detecting Key Repeat

document.addEventListener('keydown', function(event) {
    if (event.repeat) {
        console.log('Key is being held down:', event.key);
        return; // Skip repeated events
    }
    console.log('Initial key press:', event.key);
});

Building Keyboard Shortcuts

Keyboard shortcuts improve the user experience by letting power users perform actions quickly. When building keyboard shortcuts, you should combine modifier key checks with specific key values. Always call event.preventDefault() to stop the browser from performing its default action for that key combination.

Example: Implementing Keyboard Shortcuts

document.addEventListener('keydown', function(event) {
    // Ctrl+S (or Cmd+S on Mac) to save
    if ((event.ctrlKey || event.metaKey) && event.key === 's') {
        event.preventDefault();
        saveDocument();
        console.log('Document saved!');
    }

    // Ctrl+K to open search
    if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
        event.preventDefault();
        openSearchModal();
    }

    // Escape to close modals
    if (event.key === 'Escape') {
        closeAllModals();
    }

    // Ctrl+Z to undo
    if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
        event.preventDefault();
        if (event.shiftKey) {
            redo(); // Ctrl+Shift+Z for redo
        } else {
            undo();
        }
    }

    // Ctrl+Enter to submit form
    if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
        event.preventDefault();
        submitForm();
    }
});

function saveDocument() { /* save logic */ }
function openSearchModal() { /* modal logic */ }
function closeAllModals() { /* close logic */ }
function undo() { /* undo logic */ }
function redo() { /* redo logic */ }
function submitForm() { /* submit logic */ }
Pro Tip: When implementing keyboard shortcuts, always check for both event.ctrlKey and event.metaKey to support both Windows/Linux (Ctrl) and macOS (Command) users. This ensures your shortcuts work consistently across all operating systems.

Keyboard Navigation

Accessible websites must support keyboard navigation. Common patterns include navigating lists with arrow keys and trapping focus within modal dialogs. Here is how to implement keyboard navigation for a dropdown menu:

Example: Arrow Key Navigation in a Menu

const menuItems = document.querySelectorAll('.menu-item');
let currentIndex = 0;

document.addEventListener('keydown', function(event) {
    if (event.key === 'ArrowDown') {
        event.preventDefault();
        currentIndex = (currentIndex + 1) % menuItems.length;
        menuItems[currentIndex].focus();
    }

    if (event.key === 'ArrowUp') {
        event.preventDefault();
        currentIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;
        menuItems[currentIndex].focus();
    }

    if (event.key === 'Home') {
        event.preventDefault();
        currentIndex = 0;
        menuItems[currentIndex].focus();
    }

    if (event.key === 'End') {
        event.preventDefault();
        currentIndex = menuItems.length - 1;
        menuItems[currentIndex].focus();
    }

    if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        menuItems[currentIndex].click();
    }
});

Mouse Events Overview

Mouse events are the most common way users interact with web pages. JavaScript provides a comprehensive set of mouse events that cover clicking, double-clicking, pressing, releasing, moving, entering, leaving, and right-clicking. Here is the complete list of mouse events you need to know:

  • click -- Fires when the user clicks (press and release) the primary mouse button on an element.
  • dblclick -- Fires when the user double-clicks the primary mouse button.
  • mousedown -- Fires when any mouse button is pressed down on an element.
  • mouseup -- Fires when a mouse button is released over an element.
  • mousemove -- Fires continuously as the mouse pointer moves over an element.
  • mouseenter -- Fires once when the mouse pointer enters the boundary of an element. Does not bubble.
  • mouseleave -- Fires once when the mouse pointer leaves the boundary of an element. Does not bubble.
  • mouseover -- Fires when the mouse pointer enters an element or any of its child elements. It bubbles up the DOM.
  • mouseout -- Fires when the mouse pointer leaves an element or moves onto a child element. It bubbles up the DOM.
  • contextmenu -- Fires when the user right-clicks on an element (before the context menu appears).

Example: Listening to Common Mouse Events

const box = document.getElementById('interactive-box');

box.addEventListener('click', function(event) {
    console.log('Clicked!');
});

box.addEventListener('dblclick', function(event) {
    console.log('Double-clicked!');
});

box.addEventListener('mousedown', function(event) {
    console.log('Mouse button pressed down');
});

box.addEventListener('mouseup', function(event) {
    console.log('Mouse button released');
});

box.addEventListener('mousemove', function(event) {
    console.log('Mouse moving at:', event.clientX, event.clientY);
});

box.addEventListener('mouseenter', function(event) {
    console.log('Mouse entered the box');
});

box.addEventListener('mouseleave', function(event) {
    console.log('Mouse left the box');
});

mouseenter/mouseleave vs mouseover/mouseout

This distinction is critical and often causes confusion. The mouseenter and mouseleave events fire only when the pointer enters or leaves the target element itself. They do not bubble and do not fire when moving between child elements. In contrast, mouseover and mouseout bubble and fire when moving between child elements within the target, which can cause flickering effects if not handled properly.

Example: mouseenter vs mouseover Behavior

// HTML structure:
// <div id="parent">
//     <span id="child">Child Element</span>
// </div>

const parent = document.getElementById('parent');

// mouseenter fires once when entering the parent
parent.addEventListener('mouseenter', function() {
    console.log('mouseenter on parent');
});

// mouseover fires when entering parent AND when entering child
parent.addEventListener('mouseover', function(event) {
    console.log('mouseover on:', event.target.id);
    // Logs "parent" when entering parent
    // Logs "child" when entering child
});

// For hover effects, prefer mouseenter/mouseleave
// They are simpler and more predictable

MouseEvent Properties

The MouseEvent object provides detailed information about mouse position and which buttons were pressed. Understanding the different coordinate systems is essential for building interactive features.

Mouse Position Properties

  • event.clientX / event.clientY -- The mouse position relative to the browser viewport (the visible area of the page). These values do not change when the page is scrolled.
  • event.pageX / event.pageY -- The mouse position relative to the entire document, including the scrolled portion. If the page is scrolled down 200 pixels, pageY will be 200 more than clientY for the same physical cursor position.
  • event.offsetX / event.offsetY -- The mouse position relative to the top-left corner of the target element. This is extremely useful for drawing applications and games where you need coordinates within a specific element.
  • event.screenX / event.screenY -- The mouse position relative to the physical screen. This is rarely used in web development.

Example: Comparing Mouse Position Properties

const canvas = document.getElementById('drawing-area');

canvas.addEventListener('mousemove', function(event) {
    console.log('Viewport position:', event.clientX, event.clientY);
    console.log('Document position:', event.pageX, event.pageY);
    console.log('Element position:', event.offsetX, event.offsetY);
    console.log('Screen position:', event.screenX, event.screenY);
});

// Practical use: Display a tooltip that follows the mouse
const tooltip = document.getElementById('tooltip');
document.addEventListener('mousemove', function(event) {
    tooltip.style.left = event.pageX + 15 + 'px';
    tooltip.style.top = event.pageY + 15 + 'px';
});

Mouse Button Properties

The event.button property tells you which button triggered the event. The values are:

  • 0 -- Primary button (usually left click)
  • 1 -- Middle button (scroll wheel click)
  • 2 -- Secondary button (usually right click)
  • 3 -- Fourth button (typically browser back)
  • 4 -- Fifth button (typically browser forward)

The event.buttons property (note the plural) is a bitmask that tells you which buttons are currently pressed during a mousemove event. This is different from event.button which only tells you which button triggered the event.

Example: Detecting Which Mouse Button Was Clicked

document.addEventListener('mousedown', function(event) {
    switch (event.button) {
        case 0:
            console.log('Left button pressed');
            break;
        case 1:
            console.log('Middle button pressed');
            break;
        case 2:
            console.log('Right button pressed');
            break;
    }
});

// Using event.buttons during mousemove
document.addEventListener('mousemove', function(event) {
    // event.buttons is a bitmask:
    // 1 = primary (left), 2 = secondary (right), 4 = middle
    if (event.buttons & 1) {
        console.log('Left button is held while moving');
    }
    if (event.buttons & 2) {
        console.log('Right button is held while moving');
    }
});

Custom Context Menu

The contextmenu event fires when the user right-clicks. You can prevent the default browser context menu and display your own custom menu instead. This is commonly used in web applications like online editors, file managers, and design tools.

Example: Building a Custom Context Menu

const customMenu = document.getElementById('custom-menu');

document.addEventListener('contextmenu', function(event) {
    event.preventDefault();

    customMenu.style.display = 'block';
    customMenu.style.left = event.pageX + 'px';
    customMenu.style.top = event.pageY + 'px';
});

// Hide the custom menu when clicking elsewhere
document.addEventListener('click', function() {
    customMenu.style.display = 'none';
});

// Hide on Escape key
document.addEventListener('keydown', function(event) {
    if (event.key === 'Escape') {
        customMenu.style.display = 'none';
    }
});

// Handle menu item clicks
customMenu.addEventListener('click', function(event) {
    const action = event.target.dataset.action;
    if (action === 'copy') {
        console.log('Copy action triggered');
    } else if (action === 'paste') {
        console.log('Paste action triggered');
    } else if (action === 'delete') {
        console.log('Delete action triggered');
    }
    customMenu.style.display = 'none';
});

Building a Drawing Canvas

Combining mousedown, mousemove, and mouseup events allows you to build drawing functionality. This is a common real-world pattern used in signature pads, whiteboards, and design tools. The key is to track whether the user is currently drawing (mouse button held down) and capture the mouse position to draw lines.

Example: Simple Drawing Canvas with Mouse Events

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;

canvas.addEventListener('mousedown', function(event) {
    isDrawing = true;
    lastX = event.offsetX;
    lastY = event.offsetY;
});

canvas.addEventListener('mousemove', function(event) {
    if (!isDrawing) return;

    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(event.offsetX, event.offsetY);
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 3;
    ctx.lineCap = 'round';
    ctx.stroke();

    lastX = event.offsetX;
    lastY = event.offsetY;
});

canvas.addEventListener('mouseup', function() {
    isDrawing = false;
});

canvas.addEventListener('mouseleave', function() {
    isDrawing = false;
});

Drag Detection

Detecting drag operations requires combining mousedown, mousemove, and mouseup events with position tracking. A common pattern is to set a minimum distance threshold before considering the movement a drag, to distinguish drags from regular clicks.

Example: Implementing Drag Detection with Threshold

const draggable = document.getElementById('draggable-element');
let isDragging = false;
let startX = 0;
let startY = 0;
const DRAG_THRESHOLD = 5; // Minimum pixels to consider it a drag

draggable.addEventListener('mousedown', function(event) {
    startX = event.clientX;
    startY = event.clientY;
    isDragging = false;

    function onMouseMove(e) {
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        const distance = Math.sqrt(dx * dx + dy * dy);

        if (distance > DRAG_THRESHOLD) {
            isDragging = true;
            draggable.style.transform =
                'translate(' + dx + 'px, ' + dy + 'px)';
        }
    }

    function onMouseUp() {
        document.removeEventListener('mousemove', onMouseMove);
        document.removeEventListener('mouseup', onMouseUp);

        if (isDragging) {
            console.log('Drag completed');
        } else {
            console.log('It was a click, not a drag');
        }
    }

    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
});
Pro Tip: Always attach the mousemove and mouseup listeners to the document rather than the element being dragged. If you attach them to the element, the events stop firing when the mouse moves faster than the element can follow, causing the drag to break.

Introduction to Pointer Events

Pointer events are a modern, unified API that handles input from a mouse, pen/stylus, or touch screen through a single set of events. They were designed to replace separate mouse and touch event handlers with one consistent interface. The main pointer events mirror mouse events:

  • pointerdown -- Replaces mousedown and touchstart
  • pointerup -- Replaces mouseup and touchend
  • pointermove -- Replaces mousemove and touchmove
  • pointerenter -- Replaces mouseenter
  • pointerleave -- Replaces mouseleave
  • pointerover -- Replaces mouseover
  • pointerout -- Replaces mouseout
  • pointercancel -- Fires when the pointer interaction is cancelled

Example: Using Pointer Events for Cross-Device Support

const element = document.getElementById('interactive');

element.addEventListener('pointerdown', function(event) {
    console.log('Pointer type:', event.pointerType); // "mouse", "touch", or "pen"
    console.log('Pointer ID:', event.pointerId);
    console.log('Pressure:', event.pressure); // 0 to 1 for touch/pen
    console.log('Width:', event.width);   // Contact width
    console.log('Height:', event.height); // Contact height
    element.setPointerCapture(event.pointerId);
});

element.addEventListener('pointermove', function(event) {
    console.log('Moving at:', event.clientX, event.clientY);
});

element.addEventListener('pointerup', function(event) {
    element.releasePointerCapture(event.pointerId);
    console.log('Pointer released');
});
Note: Pointer events include extra properties not available in mouse events, such as pointerType (which tells you if the input came from a mouse, touch, or pen), pressure (useful for drawing apps), and pointerId (which lets you track multiple simultaneous touches). If you need to support multi-touch or stylus input, pointer events are the recommended approach.

Touch Events Basics

Touch events are specific to touch-enabled devices like smartphones and tablets. While pointer events are the modern preferred approach, understanding touch events is still valuable because many existing codebases use them. The primary touch events are:

  • touchstart -- Fires when a finger touches the screen.
  • touchmove -- Fires continuously as a finger moves across the screen.
  • touchend -- Fires when a finger is lifted off the screen.
  • touchcancel -- Fires when a touch interaction is interrupted (for example, by a phone call).

Touch events provide a touches property that contains a list of all current touch points, a targetTouches property for touches on the target element, and a changedTouches property for touches that changed in this event.

Example: Handling Basic Touch Events

const touchArea = document.getElementById('touch-area');

touchArea.addEventListener('touchstart', function(event) {
    event.preventDefault(); // Prevent scrolling
    const touch = event.touches[0];
    console.log('Touch started at:', touch.clientX, touch.clientY);
    console.log('Number of fingers:', event.touches.length);
});

touchArea.addEventListener('touchmove', function(event) {
    event.preventDefault();
    const touch = event.touches[0];
    console.log('Touch moving at:', touch.clientX, touch.clientY);
});

touchArea.addEventListener('touchend', function(event) {
    const touch = event.changedTouches[0];
    console.log('Touch ended at:', touch.clientX, touch.clientY);
});

Real-World Example: Keyboard-Navigable Tab Panel

Let us build a fully keyboard-accessible tab panel that follows the WAI-ARIA authoring practices. Users can navigate between tabs with arrow keys and activate them with Enter or Space.

Example: Accessible Tab Panel with Keyboard Navigation

// HTML structure:
// <div role="tablist">
//     <button role="tab" id="tab-1" aria-selected="true">Tab 1</button>
//     <button role="tab" id="tab-2" aria-selected="false">Tab 2</button>
//     <button role="tab" id="tab-3" aria-selected="false">Tab 3</button>
// </div>
// <div role="tabpanel" id="panel-1">Content 1</div>
// <div role="tabpanel" id="panel-2" hidden>Content 2</div>
// <div role="tabpanel" id="panel-3" hidden>Content 3</div>

const tabs = document.querySelectorAll('[role="tab"]');
const panels = document.querySelectorAll('[role="tabpanel"]');

function activateTab(tab) {
    // Deactivate all tabs
    tabs.forEach(function(t) {
        t.setAttribute('aria-selected', 'false');
        t.setAttribute('tabindex', '-1');
    });
    panels.forEach(function(p) {
        p.hidden = true;
    });

    // Activate the selected tab
    tab.setAttribute('aria-selected', 'true');
    tab.setAttribute('tabindex', '0');
    tab.focus();

    const panelId = tab.id.replace('tab', 'panel');
    document.getElementById(panelId).hidden = false;
}

tabs.forEach(function(tab, index) {
    tab.addEventListener('keydown', function(event) {
        let newIndex;
        if (event.key === 'ArrowRight') {
            newIndex = (index + 1) % tabs.length;
        } else if (event.key === 'ArrowLeft') {
            newIndex = (index - 1 + tabs.length) % tabs.length;
        } else if (event.key === 'Home') {
            newIndex = 0;
        } else if (event.key === 'End') {
            newIndex = tabs.length - 1;
        } else {
            return;
        }
        event.preventDefault();
        activateTab(tabs[newIndex]);
    });

    tab.addEventListener('click', function() {
        activateTab(tab);
    });
});

Real-World Example: Interactive Tooltip on Hover

Here is a practical example that combines mouse position tracking with mouseenter and mouseleave events to create a dynamic tooltip that follows the cursor when hovering over elements with a data-tooltip attribute.

Example: Dynamic Tooltip Following the Mouse

// Create the tooltip element
const tooltip = document.createElement('div');
tooltip.className = 'custom-tooltip';
tooltip.style.position = 'absolute';
tooltip.style.display = 'none';
tooltip.style.pointerEvents = 'none';
document.body.appendChild(tooltip);

// Add listeners to all elements with data-tooltip attribute
const targets = document.querySelectorAll('[data-tooltip]');

targets.forEach(function(target) {
    target.addEventListener('mouseenter', function() {
        tooltip.textContent = this.dataset.tooltip;
        tooltip.style.display = 'block';
    });

    target.addEventListener('mousemove', function(event) {
        tooltip.style.left = event.pageX + 12 + 'px';
        tooltip.style.top = event.pageY + 12 + 'px';
    });

    target.addEventListener('mouseleave', function() {
        tooltip.style.display = 'none';
    });
});

Preventing Default Behavior and Event Propagation

When working with keyboard and mouse events, you often need to prevent the browser's default behavior or stop the event from propagating up the DOM tree. Use event.preventDefault() to stop default actions (like form submission on Enter or page scroll on Space) and event.stopPropagation() to stop the event from bubbling to parent elements.

Example: Preventing Defaults in Practice

// Prevent form submission on Enter in a search field
const searchField = document.getElementById('search');
searchField.addEventListener('keydown', function(event) {
    if (event.key === 'Enter') {
        event.preventDefault();
        performSearch(this.value);
    }
});

// Prevent page scroll when pressing Space on a custom button
const customButton = document.getElementById('play-btn');
customButton.addEventListener('keydown', function(event) {
    if (event.key === ' ') {
        event.preventDefault();
        togglePlayback();
    }
});

// Prevent text selection on double-click
const panel = document.getElementById('resize-panel');
panel.addEventListener('mousedown', function(event) {
    if (event.detail > 1) {
        event.preventDefault(); // Prevents double-click text selection
    }
});

function performSearch(query) { console.log('Searching:', query); }
function togglePlayback() { console.log('Toggled playback'); }
Common Mistake: Overusing event.preventDefault() on keyboard events can break native browser behavior and accessibility. For example, preventing the Tab key from working removes keyboard navigation entirely. Only prevent default behavior when you have a specific reason and always ensure there is an alternative way for users to accomplish the action.

Performance Considerations for Mouse Events

The mousemove event fires extremely frequently -- often 60 times per second or more as the user moves their mouse. If your event handler does heavy computation, this can cause performance issues and jank. There are several strategies to address this:

Example: Throttling mousemove for Performance

// Strategy 1: Use requestAnimationFrame
let ticking = false;
let mouseX = 0;
let mouseY = 0;

document.addEventListener('mousemove', function(event) {
    mouseX = event.clientX;
    mouseY = event.clientY;

    if (!ticking) {
        requestAnimationFrame(function() {
            // Perform expensive update here
            updateElementPosition(mouseX, mouseY);
            ticking = false;
        });
        ticking = true;
    }
});

// Strategy 2: Throttle with a time interval
function throttle(func, limit) {
    let inThrottle = false;
    return function() {
        if (!inThrottle) {
            func.apply(this, arguments);
            inThrottle = true;
            setTimeout(function() {
                inThrottle = false;
            }, limit);
        }
    };
}

document.addEventListener('mousemove', throttle(function(event) {
    console.log('Throttled position:', event.clientX, event.clientY);
}, 50)); // Run at most every 50ms

function updateElementPosition(x, y) {
    const el = document.getElementById('follower');
    el.style.left = x + 'px';
    el.style.top = y + 'px';
}

Practice Exercise

Build an interactive web application that uses both keyboard and mouse events. Create a page with a 400 by 400 pixel canvas element. Implement the following features: (1) Users can draw on the canvas by holding down the left mouse button and moving the mouse. (2) Pressing the C key clears the canvas. (3) Pressing the number keys 1 through 5 changes the brush color to five different colors of your choice. (4) Holding Shift while drawing makes the brush size larger. (5) Right-clicking on the canvas shows a custom context menu with options to clear, save as image, and change background color. (6) Display the current mouse coordinates relative to the canvas in a status bar below the canvas. (7) Add keyboard shortcut Ctrl+Z to undo the last stroke. Store each stroke as an array of points and redraw all strokes minus the last one when undoing. Test that your application works smoothly and that all keyboard shortcuts function correctly.