Giving your users the option to preview an image, like a profile picture, can be a great UX improvement. It will also give them a preview on how the image will look within your UI. Specifically useful for the ever fully-rounded profile picture (where it could just look a bit off).
It works like this:
- give a user the option to upload a profile picture;
- after selecting an image (via a
input type=file
); - a preview of the image is shown.
Modern JavaScript comes with the FileReader interface that makes this easy. Let’s wrap it in a small Stimulus controller, ready to be copied & pasted in your Rails app.
Let’s look at the required HTML needed first:
<div data-controller="image-preview">
<img data-image-preview-target="canvas" hidden class="object-cover size-48">
<input type="file" accept="image/*" data-image-preview-target="source" data-action="image-preview#show">
</div>
Nothing special here. Instantiate the image-preview
controller and set the targets: canvas
(where the image will be previewed; hidden by default) and a source
target. Notice that this input-element also uses a accept
attribute that only allows image-types.
And then the full Stimulus controller.
// app/javascripts/controllers/image_preview_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["canvas", "source"];
show() {
const reader = new FileReader();
reader.onload = function () {
this.canvasTarget.removeAttribute("hidden");
this.canvasTarget.src = reader.result;
}.bind(this)
reader.readAsDataURL(this.sourceTarget.files[0]);
}
}
The show()
function sets up a FileReader
. When the image is loaded, the onload
function is triggered, which updates the canvas
target by setting its src
attribute to the loaded file’s result and removing the hidden
attribute. Then the file is read, converting it to a Data URL (which can be used directly in img-tags or other elements that accept source attributes).
And that’s it really! 🤯 From here you can make some UI and UX improvements:
- beautify the input-element with CSS;
- show a default/fallback profile picture;
- validate there’s only one image loaded;
- add an option to remove the profile picture (really straight-forward to do with TurboStream responses!).
Or you can try the Image Preview Component from Rails Designer (feel free to steal some CSS ideas 😅).
Check out this article if you need a way to drag and drop images with this preview image controller.