Cards with Randomly Selected Backgrounds

Lotto card

One of the enhancements to TAC v3 is that the cards on the home page have randomly selected backgrounds, no two the same. This tutorial shows how it's done.

In looking at this site's home page and removing all the contents of each card other than the background, you can more easily see the result we will be trying to achieve:

The home page card backgrounds
Randomly selected backgrounds on The Accidental Coder home page

 

There are a few items necessary to accomplish this:

  1. a set of background images
  2. a CSS style for each background image
  3. a PHP function that determines which background image to use
  4. a hook that sets the element's background
  5. a Twig template for the element that receives the background

1. Background images

You should have enough background images to account for the number of elements that will receive one. It would be even better to have more, so that the selection is not always a reordering of the same ones. TAC currently has 6 cards and fifteen background images available.

The images do not have to have similar filenames, do not have to be the same image type (i.e. one could be png and another jpg).

TAC has two versions of each background file, a webp and a jpg for browsers that don't support webp. This will be discussed in the next section.

The files do not need to be in the same folder. Of course, doing all three, anyway, might make things easier for you in the long run in terms of organization.

 

2. CSS Styles

The CSS style for each background is fairly simplistic. The styling for a single background on TAC looks like this:

.card-bg-1 {
  background-image: url('/themes/custom/tac/images/cards/bg1.webp');
  background-size: cover;

 There is a caveat, though. As I mentioned earlier, I am making use of webp images on browsers that support them. This image format is smaller than the others, so often a better choice. The way to determine whether a browser supports webp is via JavaScript. There are actually 3 scenarios that need to be handled via CSS rules:

  • The browser supports webp
  • The browser does not support webp
  • The browser does not have JavaScript enabled, so determining webp support is not possible

TAC is currently using the Gesso theme, which does the lifting of determining which of the three scenarios apply. The result is that the applicable classname webpno-webp, or no-js is added, and so the proper rule for background 1 would need to match one of the resulting class possibilities:

  • .webp .card-bg-1
  • .no-webp .card-bg-1
  • .no-js .card-bg-1

The rules, then, are as follows:

.webp .card-bg-1 {
  background-image: url('/themes/custom/tac/images/cards/bg1.webp');
  background-size: cover;
}
.no-js .card-bg-1,
.no-webp .card-bg-1 {
  background-image: url('/themes/custom/tac/images/cards/bg1.jpg');
  background-size: cover;
}

3. PHP background selection function

We'll create the function inside a custom module. It can be in the .module file or some other included file.

function get_card_background() {
}

No parameters need to be passed to this function.

When selecting the background, the desire is to make a random choice from those available, but ensure that no subsequent choice made results in the same background being selected. That is, if we have 6 cards and the first card uses background 1, none of the remaining five should use background 1.

The way we will do this is via a static variable.

  static $background;

The value of this variable, once set, will be retained between calls to the function. Why that's important will shortly be evident. First, let's initialize $background. If it were going to contain a value, such as 0, we could simply have stated:

  static $background = 0;

The nice thing about a static variable is that its initialization is only done the first time the function containing it is called. However, in our case, we need to make it an array, and unfortunately arrays cannot be used as a value in a static variable statement. We'll need to do it this way:

  if (!is_array($background)) {
    $background = range(1, 15);
  }

Since the only time the variable will not contain an array is the first time through the function, the if statement will only be executed that one time. We use the range function to create an array of values 1 thru 15. We could have used a for loop to achieve the same result.

The purpose of the array is to select a value from it that determines the background image to use. We want the selection to be random...like a PHP lottery. We achieve that using the array_rand function. 

  $idx = array_rand($background);
  $selection = $background[$idx];

This gives us a randomly selected index into the $background array, which we use to obtain the background value it contains.

Next, we need to ensure that the value we just selected will not be selected on subsequent function calls. To do this we simply remove the index from the array...accomplishing a destructive read, of sorts.

  unset($background[$idx]);

One last thing, which is to return the selected value to the calling function:

  return $selection;

Here is the result of calling the function six times, as is done on TAC.

4
14
10
5
6
3

And here is the complete function:

function get_card_background() {
  static $background;

  if (!is_array($background)) {
    $background = range(1, 15);
  }

  $idx = array_rand($background);
  $selection = $background[$idx];
  unset($background[$idx]);

  return $selection;
}

4. The hook

At the moment we have a function that returns a background value, but nothing calling it. What we want to do is call that function whenever an element on the page needs one of the backgrounds assigned. In the case of TAC, each of the cards comes from a separate view, each resulting in one node being selected. So, the best place from which to call the function is a point at which the node has been chosen but not yet rendered, because we want to let the background value hitch a ride on the node to the template.

function hook_preprocess_node(&$variables) {

You will, of course, need to replace hook with the name of your module or theme. In the case of TAC, the nodes needing a background are cards and make use of a view mode called card, so we will only process nodes using that mode...which means its a card.

  if ($variables['content']['body']['#view_mode'] == 'card') {
  }

Once we know that we're working with a card, we can add our background value to it:

  if ($variables['content']['body']['#view_mode'] == 'card') {
    $variables['content']['card_background'] = get_card_background();
  }

That's all there is to the hook. We don't have to return $variables, because it is passed by reference.

5. Twig template

The final piece is the entry in the twig template that provides the markup for the elements receiving the background.

We've passed the background value as part of content in a variable called card_background. What we want to do is complete the linkage between that variable's value and the markup that names the class, which ties it to the CSS style we created earlier. Doing so is as simple as this:

    <div class="card__side front card-bg-{{ content.card_background }}">

The result of this, in the case of the background value being 1, is the classname card-bg-1.

And Bob's your uncle!


Type:
Tutorial
Tags:
D9
theming
Drupal Planet