Image Filter Editor HTML CSS JS — CSS Filters

Image Filter Editor · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

FileReader API: readAsDataURL loads images fully client-side with no upload
Drag-and-drop: DataTransfer events accept dropped files with preventDefault navigation guard
CSS filter composition: a single string of brightness, contrast, saturate, blur, hue-rotate, sepia, grayscale and invert
GPU-accelerated preview: style.filter updates instantly even on large images
Canvas ctx.filter export: the same filter string rasterizes a pixel-identical result
Natural-resolution output: canvas sized to naturalWidth and naturalHeight for full quality
PNG download: toDataURL plus a download anchor triggers the save dialog
Reset to defaults: restores every slider from a single FILTERS source of truth
Randomize: assigns each filter a random in-range value for quick discovery
Copyable CSS string: the live filter declaration is ready to paste into stylesheets

About this UI Snippet

How to Build an Image Filter Editor with CSS Filters and Canvas

Screenshot of the Image Filter Editor snippet rendered live

An image filter editor lets users upload a photo, adjust brightness, contrast, saturation, blur, hue, sepia, grayscale and invert with live sliders, and download the edited result. The clever part is that all the heavy lifting is done by the browser's built-in CSS filter engine for the live preview, and by the matching Canvas `ctx.filter` property for the final export — so there is no per-pixel JavaScript image processing and no library at all. Here is how it is built.

Loading the image with FileReader and drag-and-drop

Images enter the editor two ways. A file input fires a change event, and a drop zone listens for the DataTransfer API events dragover, dragleave and drop. In the drop handler, event.dataTransfer.files[0] gives the dropped file, and event.preventDefault() stops the browser from navigating away to open it. Both paths funnel into a single loader that uses the FileReader API: reader.readAsDataURL(file) reads the image into a base64 data URL, and on reader.onload that URL is assigned to an Image element's src. Reading as a data URL keeps everything client-side — the file never leaves the browser.

Composing the CSS filter string

The editor defines a FILTERS array describing each adjustment: its CSS function name (brightness, contrast, saturate, blur, hue-rotate, sepia, grayscale, invert), the slider range, the default value, and the unit (%, px or deg). Each filter is rendered as a labeled range input.

On any slider change, the code rebuilds a single CSS filter string by mapping over the array: for each filter it produces a token like brightness(120%) or blur(3px), then joins them with spaces into one declaration such as brightness(120%) contrast(110%) saturate(150%) blur(0px) hue-rotate(0deg) sepia(0%) grayscale(0%) invert(0%). Assigning that string to the preview image's style.filter applies every adjustment at once. The order of functions in the string matters — CSS filters are applied left to right, so each operates on the output of the previous — which is why the array order is fixed and meaningful.

Why CSS filters instead of pixel manipulation

A naive editor would call getImageData, loop over millions of bytes adjusting each channel, and putImageData back — slow and complex. By contrast, CSS filters are GPU-accelerated and free: the browser already implements brightness, contrast, hue rotation, sepia matrices and Gaussian blur natively. The live preview updates instantly even on large images because the compositor does the work, and the code stays tiny since it only assembles a string.

Exporting with Canvas ctx.filter

The catch is that style.filter only affects how the element is displayed — it does not change the underlying image data, so you cannot just save the <img>. To produce a real filtered file, the export step draws to a canvas. It creates an offscreen <canvas> sized to the image's naturalWidth and naturalHeight (the true pixel dimensions, not the displayed size), gets the 2D context, and sets ctx.filter to the exact same composed filter string used for the preview. Then ctx.drawImage(previewImg, 0, 0) rasterizes the image through the filter pipeline directly into the canvas bitmap. Because ctx.filter accepts the same syntax as the CSS property, the canvas output is pixel-identical to what the user sees.

Finally the canvas is exported with canvas.toDataURL(), which returns a PNG data URL. A temporary anchor with a download attribute and that URL as its href is clicked programmatically to trigger the browser's save dialog. This canvas-to-anchor pattern is the standard way to download generated images.

Reset and randomize

A reset button restores every slider to its default value from the FILTERS array and rebuilds the filter string, returning the image to its original look. A randomize button assigns each filter a random value within its range and reapplies, which is a fun way to discover combinations. Both simply mutate the slider values and call the same rebuild routine, demonstrating how a single source of truth — the filter string assembled from the inputs — keeps preview, export, reset and randomize perfectly in sync.

A copyable filter string

Because the live CSS filter string is exactly what a developer would paste into a stylesheet, the editor also surfaces it as text. Users can copy the current filter: declaration and drop it straight into their own CSS, making the tool double as a visual generator for CSS filter values.

Altogether the editor shows how to combine the FileReader API, the DataTransfer drag-and-drop API, GPU-accelerated CSS filters for instant preview, and canvas ctx.filter plus toDataURL for true export — a complete, dependency-free image editing flow in a few dozen lines.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out why the preview and the download can differ without careful handling. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why style.filter on the img element is not enough to produce a downloadable filtered file, and how setting the identical filter string on a canvas context's ctx.filter property before drawImage solves that. The same assistant is useful for optimizing it — ask whether rebuilding the entire filter string and updating every label on every single slider input event could be debounced or throttled for a very high-resolution preview image without hurting responsiveness. It's just as handy for extending the editor: ask it to add filter presets (like "vintage" or "cool") that set multiple sliders at once, support undo/redo through a history of filter-string snapshots, or add a side-by-side before/after view using the same image element twice. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a CSS-filter based image editor in plain HTML, CSS, and JavaScript — no image-processing library, no pixel manipulation loops.

Requirements:
- A drop zone that accepts an image both via a hidden file input (triggered by clicking the zone) and via native drag-and-drop events, loading the file through FileReader's readAsDataURL method and displaying it in an img element.
- Define a single array of filter descriptors, each with a CSS filter function name (like brightness, contrast, saturate, blur, hue-rotate, sepia, grayscale, invert), a minimum, a maximum, a default value, and a unit (%, px, or deg). Generate one labeled range slider per descriptor from this array rather than hand-writing each slider.
- On any slider's input event, rebuild one combined CSS filter string by mapping over every filter descriptor in a fixed order and joining function-call tokens together (e.g. "brightness(120%) contrast(110%) blur(2px)"), then apply that exact string to the preview image's style.filter and also display it as copyable plain text.
- Add a Reset button that restores every slider to its descriptor's default value and rebuilds the filter string, and a Randomize button that assigns each slider a random value within its own min/max range and rebuilds the filter string.
- Add a Download button that creates an off-screen canvas sized to the image's natural (full) width and height (not its displayed size), sets the canvas context's filter property to the exact same composed filter string used for the live preview, draws the image onto that canvas, and triggers a PNG download from the canvas's data URL — so the exported file is pixel-identical to the on-screen preview at full resolution.

Step by step

How to Use

  1. 1
    Add an imageDrag a photo onto the drop zone or click to pick one with the file dialog.
  2. 2
    Adjust the slidersMove the brightness, contrast, saturate, blur, hue, sepia, grayscale and invert sliders to taste.
  3. 3
    Watch the live previewSee changes applied instantly as the composed CSS filter string updates the image.
  4. 4
    Try randomizeClick randomize to explore unexpected filter combinations in one tap.
  5. 5
    Copy the CSSGrab the generated filter declaration to paste directly into your own stylesheet.
  6. 6
    Download the resultClick download to render the filters onto a canvas and save a PNG.

Real-world uses

Common Use Cases

Photo editing widget
Add quick, in-browser filter editing to a gallery, pairing well with a drawing canvas for annotation.
CSS filter generator
Visually dial in a filter combination and copy the exact declaration into your stylesheet.
Avatar and profile tools
Let users tweak and download a processed profile picture before upload.
Upload preprocessing
Offer brightness and contrast adjustment before a user submits an image in a form.
Teaching CSS filters
Demonstrate how each filter function and its order affects an image next to a color wheel picker.
Content creation apps
Power lightweight image styling in social or publishing tools without a backend.

Got questions?

Frequently Asked Questions

CSS filters are implemented natively and GPU-accelerated, so the preview updates instantly with no per-pixel loop. Assembling a filter string is far simpler and faster than calling getImageData and processing millions of bytes by hand.

style.filter only changes how the image is displayed, not the underlying pixel data. To save a truly filtered file you draw the image through ctx.filter onto a canvas, which bakes the effect into the bitmap that toDataURL exports.

Yes. CSS filters apply left to right, each operating on the previous result, so blurring before versus after a hue rotation gives different output. The editor keeps a fixed, meaningful order in its FILTERS array.

No. FileReader reads the file into a local data URL and all processing happens in the browser via CSS and canvas. The image never leaves the device.

Those properties give the image true pixel dimensions rather than its on-screen display size, so the exported PNG is full resolution and not downscaled to the preview dimensions.

Yes. The JSX, Vue, Angular, and Tailwind exports convert it automatically. In React, keep each slider value in useState and build the CSS filter string during render; for download, draw the filtered image to a canvas inside an event handler using ctx.filter before calling toDataURL.