Multiple.js
An experiment in sharing background across multiple elements using CSS
Research by Denis Lukov, creator of Clusterize.js and Jets.js

Surfing over dribbble I stumbled upon a Xonom design by Cosmin Daniel Capitanu. I wondered how to accomplish such "background sharing across multiple elements" effect at HTML/CSS without any coordinates processing by JavaScript. This page is result of my research by this topic.

Check original Xonom and notice how background gradient shares across multiple elements. Note that icons and text at lighter layers have gradient-color from darker layers, creating the illusion of underlying violet layer. In addition to that, layers pass background image (girl) with equal transparency so that layers can not be superimposed on each other. All this things can be implemented at CSS.

Not peeping into sources, think for a moment about how this effect may be implemented at pure CSS ?

Show me how it works!

.selector {
  background-image: linear-gradient(white, black);
  background-size: cover;
  background-position: center;
  background-attachment: fixed;  /* <- here it is */

  width: 100px;
  height: 100px;
}

Idea is just that simple!

background-attachment: fixed expands background to viewport's size and displays in every element appropriate chunk, exactly what is needed!

But here we stumble upon a problem with mobile devices, as far as they seems to ignore background-attachment: fixed property for performance reasons . Background behaves as with default value background-attachment: scroll. And in addition to that there are even no way to detect support of this property.

So how to handle this flaw at mobile devices? Think a bit on possible workaround and click a button below.

Mobile workaround

I bet the first thing you think was position: fixed; z-index: -1; trick because this is frequent solution for such background-related cases.

Ok, let's assume we've added pseudo element with such styles

.selector {
  width: 100px;
  height: 100px;
}

.selector:before {
  content: '';
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;

  background-image: linear-gradient(white, black);
  background-size: cover;
}

Background has been successfully expanded to full viewport size . But we need to display only that chunk of background under .selector element. It turns out that background outside of .selector element must be cropped.

But how to do that? .selector { overflow: hidden; } won't do the trick because it's descendant is position: fixed. Check out by yourself at Codepen

Go further

A little known fact but position: fixed can be cropped by clip: rect() property! See

So that

.selector {
  position: absolute;
  clip: rect(0, auto, auto, 0);

  width: 100px;
  height: 100px;
}

.selector:before {
  content: '';
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;

  background-image: linear-gradient(white, black);
  background-size: cover;
}

Single rule - parent element must be position: absolute or position: fixed, otherwise clip: rect() won't crop.

But because of that limitation all .selector at mobile devise must be position: absolute while desktop version does not have this restriction. This is not convenient at all. Elements must behave identically for both desktop and mobiles and does not contain extra conditions.

That's why Multiple.js was written, this little JS plugin takes care about all those markup differences, browser specific workarounds, vendor-prefixes, etc.. for you. All you left to do is simply specify selector and background.

If for some reason you decide to avoid JS plugin, want only pick styles and do all markup work by yourself, here are starting material for you:

Show markup/styles

For desktop Codepen

<div class="selector multiple-desktop">Your content</div>
/* where .selector is your element.
*  Give it any styles you need (any display, position, sizes...)
*  Set appropriate background-image to .multiple-desktop, don't forget about vendor prefixes.
*/
.selector {
  width: 100px;
  height: 100px;
}

.multiple-desktop {
 /* background-image: linear-gradient(rgba(133, 149, 232, 0.58), rgba(0,0,0, .5)); */
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
  background-repeat: no-repeat;
}

For mobile Codepen

<div class="selector">
  <div class="multiple-mobile-wrapper">
    <div class="multiple-mobile"></div>
    <div class="multiple-mobile-content">Your content</div>
  </div>
</div>

/* where .selector is your element.
*  Give it any styles you need (any display, position, sizes...)
*  Set appropriate background-image to .multiple-mobile:before, don't forget about vendor prefixes.
*/
.selector {
  width: 100px;
  height: 100px;
}

.multiple-mobile-wrapper {
  position: relative;
  height: 100%; 
}

.multiple-mobile-content {
  height: 100%;
}

.multiple-mobile {
  clip: rect(0 auto auto 0);
  clip: rect(0, auto, auto, 0);
  -webkit-mask-image: -webkit-linear-gradient(top, #fff 0%, #fff 100%);
  -webkit-clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  overflow: hidden;

  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

  z-index: -1;
}

.multiple-mobile:before {
  content: '';
  
 /* background-image: linear-gradient(rgba(133, 149, 232, 0.58), rgba(0,0,0, .5)); */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;

  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

And, in addition to that I'd like to share thoughts about customizing viewport size.

As you know background-attachment: fixed depends on viewport size, so background fills all screen from top left to bottom right point. Such background does not react on scroll, visually static and always located in the same place. Sometimes this behaviour is not what we need. Let's assume our whole background image must be strictly fit at element with sizes 500px x 500px (when actual viewport is 1920x1080). Or you need background to scroll with the page (as by default).

See examples to understand the difference:

  • From this (background is stretched to full-page viewport and keeps fixed on scroll while container size is restricted, so that background isn't fit wholly in container)
  • To this (background is stretched to container size so is fully fit and keeps scrolls with the page)

Any ideas how to do that?

Go ahead

And the answer is: Iframe!

It was a surprise for me to find out that iframe has it's own viewport, I just didn't thought about this from this point of view before. Wrapping the content into iframe and setting iframe specific sizes - and background will fill iframe's size, not parent page viewport size. Check out real examples: without iframe, with iframe. To do that easily may be used convenient iframe-resizer library that observes iframe's inner content height and change iframe height accordingly.

That's all! I hope you enjoy reading this :)

Quickstart

bower i multiple.js
npm i multiple.js
or cdnjs

Include it on your page

<link href="./multiple.css" rel="stylesheet">
<script src="./multiple.min.js"></script>

Usage

<!--HTML-->
<div class="item">Content</div>
<div class="item">Content</div>
<div class="item">Content</div>
// JavaScript
var multiple = new Multiple({
  selector: '.item',
  background: 'linear-gradient(#273463, #8B4256)'
});

Options

Name Required Description Example Default

selector

Required Selector. Refers to document.querySelectorAll() '.items'

background

Required Background image/gradient. Should be valid background-image CSS property. Support multiple backgrounds. Must not contain vendor prefixes for gradients, they will be added automatically. 'url(image.png), linear-gradient(pink, violet)'

affectText

Optional

Specifies if background image should affect text instead of background.

P.s. Works only at desktop webkit browsers. As fallback for mobile or other desktop browsers will be shown color specified in this property. See difference at Codepen .

'black' false

opacity

Optional

Obviously gradient's opacity may be set by RGBA syntax.

But if you are too lazy to convert HEX or RGB to RGBA, you might find this option useful. All HEX (short and long forms) and RGB colors of gradients specified in background option will be converted to RGBA with specified opacity. See difference at Codepen .

opacity supports two types of input:

  • 0.1 - number. Will be set RGBA with specified opacity 0.1
  • true - boolean. Will be calculated RGBA color with as much transparency as possible over white (in other words looks same as RGB color with transparency and without loss of color)

P.s. Works only with gradients, does not work with images.

true or 0.2 false

Methods

Name Params Description

.update()

String

Updates background image (if passed) and process newly added tags (wraps content at mobile devices, adds classes). Update background example: .update('url(image.png)');. If you've appended new items to DOM and want apply Multiple for them use .update();

.destroy()

Destroys Multiple instance, removes classes, reverts layout to original state if was modified

Browsers support

  • Any desktop browser that supports background-attachment: fixed
  • Mobile browsers that supports position: fixed and clip: rect(). I haven't chance to test it everywhere so if it doesn't work for you, create an issue and I'll update supported device list.

Please note, this is experimental library so performance of page may be low because of usage of intensive CSS properties to achieve desired effects.

Author

Denis Lukov [ GitHub | LinkedIn | Twitter ]
Contact me
Licensed under the MIT license

If you enjoyed this you might also like: