Replacing Placeholder Images with some CSS3-Fu! 🦸‍♀️


Ok, it’s the new year eve, so let’s do something lame. Yes, because why not! 🙈

Also, happy new year 2019 everyone! 😘

Update (Jan, 25th ‘19): We are no longer using the uploader code and Carrierwave gem as explained in the original article below. We migrated to using direct uploads with Cloudinary instead, even though the placeholder images are still being handled using CSS3. Exactly the way it has been explained below.

It’s fairly common in the land of consumer apps to serve a placeholder image (default_image) for new users who have signed up but not uploaded an image for themselves. Like this one below:



To achieve this, there is often an uploader method like so:

 def default_url
   unless version_name.blank?
     /assets/users/default_user/ + [version_name.downcase, default-user.png].compact.join(_)
   else
     /assets/users/default_user/ + [default-user.png].compact.join(_)
   end
 end

We were are using the classy Carrierwave gem by Jonas Nicklas for file uploads on our Rails app.

So the first step is to remove the code for default_images and figure out some CSS properties with which we can paint that sort of image client-side. I removed the entire block of code for default_url on the rails_uploader so that no http request is fired to serve a default image when the user doesn’t have an avatar for themselves.

We paint a placeholder image using CSS (SCSS) like so:

@charset "UTF-8";

.profile-icon {
	font-size: 15px;
	border: 0.1em solid #ccc;
	width: 10em;
	height: 10em;
	border-radius: 50%;
	overflow: hidden;
	position: relative;
	background: #eee;
	&:before {
		content: ‘’;
		display: block;
		position: absolute;
		border: 0.1em solid #ccc;
		background: white;
		width: 7em;
		height: 7em;
		border-radius: 50%;
		bottom: -3em;
		left: 50%;
		margin-left: -3.5em;
	}
	&:after {
		content: ‘’;
		display: block;
		position: absolute;
		width: 4em;
		height: 4em;
		border: 0.1em solid #ccc;
		border-radius: 50%;
		background: white;
		top: 1.8em;
		left: 50%;
		margin-left: -2em;
	}
}
.profile-image-container {
	height: 200px;
	width: 200px;
}
.profile-image {
	border: 5px solid #fff;
	border-radius: 50%;
	box-shadow: 0 0 26px rgba(0, 0, 0, 0.35) inset, 0 0 15px rgba(0, 0, 0, 0.35);
	height: 150px;
	left: -5px;
	top: -5px;
	width: 150px;
	input {
		background: transparent left top no-repeat;
		height: 140px;
		width: 137px;
		cursor: pointer;
		position: absolute;
		left: 0;
		z-index: 1;
		opacity: 0;
	}
	label {
		background: transparent left top no-repeat;
		height: 140px;
		width: 137px;
		cursor: pointer;
		position: absolute;
		left: 0;
		z-index: 1;
		opacity: 0;
		opacity: 0.4;
	}
}


A single class with :before & :after pseudos does it!

Then in the view layer (we use haml on Bubblin!):

- unless @user.image.blank?
  .center.profile-icon.profile-image  # ← Render a placeholder image using CSS 
    = form_for @user, remote: :true, html: { multipart: true, method: :patch, id: addImage } do |f|
    = f.label :image, New image?, class: flex;
    = f.hidden_field(:image_cache)
    = f.file_field :image, onchange: uploadImage(this), data: {max_file_size: 1.megabytes }

And boom! Gone is the overhead of serving another sorry image (jpg/png) file. Could potentially save quite a bit of bandwidth and a bunch of http requests on your app.

Sounds cool, huh? Or lame may be. Anyway, happy new year and happy coding!


Written by: Sonica Arora, CTO of Bubblin Superbooks. Follow me on Twitter perhaps?

Edited by: Marvin Danig, CEO of Bubblin Superbooks.

P.S.: It’s likely that some of you viewed this site on your desktop. If you did that we recommend revisiting us on your iPad!