---
title: We need some focus
date: 2025-03-04T17:21:48Z
modified: 2025-03-04T17:22:54Z
permalink: "https://dgw.ltd/2025/03/04/we-need-some-focus/"
type: post
status: publish
excerpt: ""
wpid: 522
categories:
  - Code
featured_image: "https://dgw.ltd/wp-content/uploads/2025/03/Screenshot-2025-03-04-at-17.15.50.png"
featured_image_alt: Screen shot of focal point picker for a custom block
---

The WordPress block editor generally gets better and better. One great example is the [Cover block](https://wordpress.com/support/wordpress-editor/blocks/cover-block/). I have tended to build out custom hero components previously but honestly this is becoming less necessary as the cover block becomes my go-to method of creating [hero sections](https://dribbble.com/tags/hero-section).

The ability to add a large image (or video), some text and maybe a button or two allows us to create a nice big intro to pages. Along with block variations we can add extra default innerBlocks to prompt the user to fill out more information.

One particularly nice feature is the ability to add focus to the background image, by motion the focal point we can make sure that the right part of the image is getting focus, so for example if it’s a person’s face we don’t just see the top of their head.

Like this:

![Screen shot of focal point picker for the Cover block](https://dgw.ltd/wp-content/uploads/2025/03/Screenshot-2025-03-04-at-16.51.16.png)

Then a little while ago I stumbled on [this post](https://henry.codes/writing/pure-css-focal-points/) by the excellent Henry Desroches. In this post he looks at how this might be achieved through JavaScript, HTML and CSS:


```js
const sourceImage = document.querySelector(".source-image");
sourceImage.addEventListener("click", (event) => {
  const rect = event.target.getBoundingClientRect();
  const xCoord = event.clientX - rect.left;
  const yCoord = event.clientY - rect.top;

const xAsPercentage = (xCoord / rect.width) _ 100;
const yAsPercentage = (yCoord / rect.height) _ 100;

document.documentElement.style.setProperty("--focus-x", `${xAsPercentage}%`);
document.documentElement.style.setProperty("--focus-y", `${yAsPercentage}%`);
});
```


```
```wp-block-code
<html style="--focus-x:64.2862%; --focus-y:35.5263%;"></html>
```


```
```wp-block-code
article > img {
  aspect-ratio: var(--aspect-ratio);
  object-fit: cover;
  object-position: var(--focus-x) var(--focus-y);
}
```

Nice.

In the post he mentions implementing a focal point selector on images in WordPress…And well, I just had to give it a go and see if I could replicate the focal point selector in a custom ACF image block.

Now, trigger warning, I did use generative AI for this. I wouldn’t have even attempted this previously it would have been outside of my ability, or more to the point my ability to not get pissed off with days worth of screaming and crying at JavaScript. I know the fundamentals around JavasScript and consider myself pretty good when it comes to building out vanilla JS for a lot of functionality. But when it comes to something like the [ACF JavaScript API](https://www.advancedcustomfields.com/resources/javascript-api/) and/or [@wordpress/scripts](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/). I would tend to get eventually bogged down and bugged out. I’ve found using generative AI really useful for this. I know what I am trying to achieve and I don’t have to visit documentation that is basically draw the rest of the fucking owl. Or visiting message boards that seem to jump all the way to framing the owl drawing.

This doesn’t mean I didn’t have to do a hella lot of debugging and investigation myself, simply that it got me to places I just wouldn’t have been able to reach on my own, or certainly more quickly than I could have justified if I was going this alone. For example the ACF Javascript API is great but I found there was an issue that because my custom field was in the sidebar and so wasn’t immediately picked up.

This was the potential solution where by I check to see if the selected block is my hero block and then perform the ACF field look up


```js
function trackBlockSelection() {
    let previousBlock = null;

    subscribe(() => {
      const selectedBlock = select('core/block-editor').getSelectedBlock();
      
      if (selectedBlock && selectedBlock.name === 'acf/dgwltd-hero') { 
        if (selectedBlock !== previousBlock) {
          console.log('Sidebar block selected:', selectedBlock);
          acfUpdateFocusPosition();
        }
      }

      previousBlock = selectedBlock;
    });
  }
```

For this to work there are three custom fields, background\_image, focal\_point\_x, focal\_point\_y. Clicking on the background image changes the focal point and populates the focal point coordinates.

I am sure there are loads of issues with this code and I probably wouldn’t use this in production or client sites, but as a learning experience it was really useful and I felt using generative AI on this enhanced my understanding on how the block editor works and how we can leverage ACF Javascript API to create more advanced components.

This is this end product:

![Screen shot of focal point picker for the my custom block with two focal point X and Y fields](https://dgw.ltd/wp-content/uploads/2025/03/Screenshot-2025-03-04-at-17.19.45.png)

Here is the full code:


```js
// Import select and dispatch from wp-data to access block meta
import { select, dispatch, subscribe } from '@wordpress/data';

(function () {
  'use strict';

  const acfUpdateFocusPosition = () => {  
    // Get the selected block
    const selectedBlock = select('core/block-editor').getSelectedBlock();
    
    if (!selectedBlock || selectedBlock.name !== 'acf/dgwltd-hero') {
      console.warn('Selected block is not the hero block.');
      return;
    }

    const { clientId, attributes } = selectedBlock;

    if (!attributes || !attributes.data) {
      console.warn('No ACF data found in the selected block.');
      return;
    }

    // Extract the fields
    const { background_image, focal_point_x, focal_point_y } = attributes.data;

    if (!background_image) {
      console.warn('No background image found for this block.');
      return;
    }

    // Find the image inside the ACF sidebar
    setTimeout(() => {
      const sidebarImageWrap = document.querySelector('.acf-field-image[data-name="background_image"] .image-wrap');
      const sidebarImage = sidebarImageWrap ? sidebarImageWrap.querySelector('img') : null;

      if (!sidebarImage || !sidebarImageWrap) {
        console.warn('Could not find the background image in the ACF sidebar field.');
        return;
      }

      console.log('Background Image Found in Sidebar:', sidebarImage);

      // Create or select the focal point indicator dot
      let focusDot = sidebarImageWrap.querySelector('.focus-dot');
      if (!focusDot) {
        focusDot = document.createElement('div');
        focusDot.classList.add('focus-dot');
        sidebarImageWrap.appendChild(focusDot);
      }

      // Apply initial position based on saved focal points
      focusDot.style.left = `${focal_point_x}%`;
      focusDot.style.top = `${focal_point_y}%`;

      // If a previous event handler exists, remove it
      if (sidebarImage.dataset.eventAttached === "true") {
        console.log("Event listener already attached, skipping...");
        return; // Prevent duplicate event listeners
      }

      // Define the event handler
      const handleImageClick = (event) => {
        const rect = event.target.getBoundingClientRect();
        const xCoord = event.clientX - rect.left;
        const yCoord = event.clientY - rect.top;

        const xAsPercentage = (xCoord / rect.width) * 100;
        const yAsPercentage = (yCoord / rect.height) * 100;

        // Update the corresponding ACF input fields in the sidebar
        const focalXInput = document.querySelector('.acf-field-number[data-name="focal_point_x"] input');
        const focalYInput = document.querySelector('.acf-field-number[data-name="focal_point_y"] input');

        // Update dot position
        focusDot.style.left = `${xAsPercentage}%`;
        focusDot.style.top = `${yAsPercentage}%`;

        if (focalXInput && focalYInput) {
          focalXInput.value = xAsPercentage.toFixed(2);
          focalYInput.value = yAsPercentage.toFixed(2);

          // Trigger input event to notify ACF of the change
          focalXInput.dispatchEvent(new Event('input', { bubbles: true }));
          focalYInput.dispatchEvent(new Event('input', { bubbles: true }));

          console.log(`Updated Focal Point X: ${xAsPercentage.toFixed(2)}%`);
          console.log(`Updated Focal Point Y: ${yAsPercentage.toFixed(2)}%`);
        } else {
          console.warn("Could not find ACF focal point input fields.");
        }

        // Dispatch update to block attributes in the editor
        dispatch("core/block-editor").updateBlockAttributes(clientId, {
          data: {
            ...attributes.data,
            focal_point_x: xAsPercentage.toFixed(2),
            focal_point_y: yAsPercentage.toFixed(2)
          }
        });

        console.log("Updated ACF Focal Points in Block:", {
          focal_point_x: xAsPercentage.toFixed(2),
          focal_point_y: yAsPercentage.toFixed(2)
        });
      };

      // Attach the event listener only if it hasn't been attached already
      sidebarImage.addEventListener('click', handleImageClick);
      sidebarImage.dataset.eventAttached = "true"; // Mark the event as attached

      console.log('Click event attached to background image in sidebar.');
    }, 500); // Delay to ensure ACF loads fully
  };

  function whenEditorIsReady() {
    return new Promise((resolve) => {
      const unsubscribe = subscribe(() => {
        if (select('core/block-editor').getBlockCount() > 0) {
          unsubscribe();
          resolve();
        }
      });
    });
  }

  function trackBlockSelection() {
    let previousBlock = null;

    subscribe(() => {
      const selectedBlock = select('core/block-editor').getSelectedBlock();
      
      if (selectedBlock && selectedBlock.name === 'acf/dgwltd-hero') { 
        if (selectedBlock !== previousBlock) {
          console.log('Sidebar block selected:', selectedBlock);
          acfUpdateFocusPosition();
        }
      }

      previousBlock = selectedBlock;
    });
  }

  document.addEventListener("DOMContentLoaded", function() {
    acf.add_action('ready', function() {
      whenEditorIsReady().then(() => {
        trackBlockSelection(); 
      });
    });
  });

})();
```