Developer's Guide 

This chapter covers the technical aspects of integrating the Focal Point Editor in your TYPO3 templates and custom extensions.

Namespace 

The extension uses the namespace:

Nng\Nnfocalpoint
Copied!

ViewHelpers 

The extension provides six ViewHelpers for easy frontend integration.

CSS Styles 

The extension includes a frontend CSS file with basic styles. Include it via TypoScript:

page.includeCSS.nnfocalpoint = EXT:nnfocalpoint/Resources/Public/Css/Frontend.css
Copied!

Or include the static TypoScript "Focal Point Editor" in your template.

Available CSS classes:

  • .nnfp-image - Automatically added to images from nnfp:image (width/height 100%, display block)
  • .nnfp-container - Container with overflow hidden for focal point images
  • .nnfp-container--16-9, --4-3, --1-1, --3-2, --21-9 - Aspect ratio helpers
  • .nnfp-bg - Background image helper (background-size: cover, no-repeat)

Registering the Namespace 

The extension registers a global namespace nnfp automatically. You can use the ViewHelpers without any namespace declaration:

<!-- Global namespace - no declaration needed -->
<nnfp:image image="{image}" width="400" />
<div style="{nnfp:backgroundStyle(image: image)}">
Copied!

Alternatively, you can use the full namespace declaration:

{namespace nnfocalpoint=Nng\Nnfocalpoint\ViewHelpers}
Copied!

Or use the XML namespace syntax:

<html xmlns:nnfocalpoint="http://typo3.org/ns/Nng/Nnfocalpoint/ViewHelpers"
      data-namespace-typo3-fluid="true">
Copied!

ImageViewHelper (Drop-in f:image replacement) 

A drop-in replacement for f:image that automatically adds focal point styling. All standard f:image arguments are supported.

Class: Nng\Nnfocalpoint\ViewHelpers\ImageViewHelper

Additional Arguments:

Argument Type Required Description
objectFit string No CSS object-fit value (default: cover)
disableFocalPoint bool No Set to true to disable focal point styling

Usage:

<!-- Simple usage - replaces f:image -->
<nnfp:image image="{image}" width="400" height="300" />

<!-- With crop variant -->
<nnfp:image image="{image}" width="800c" height="600c" cropVariant="mobile" />

<!-- With custom object-fit -->
<nnfp:image image="{image}" width="400" objectFit="contain" />

<!-- Disable focal point (use as regular f:image) -->
<nnfp:image image="{image}" width="400" disableFocalPoint="true" />
Copied!

Output:

<img src="/fileadmin/_processed_/image.jpg" 
     width="400" height="300"
     class="nnfp-image"
     style="object-fit: cover; object-position: 50% 30%;" />
Copied!

FocalCropViewHelper (Server-side focal crop) 

Crops images to exact dimensions while keeping the focal point centered. Unlike nnfp:image which uses CSS positioning, this ViewHelper performs actual server-side cropping - the generated image file has the exact target dimensions.

Class: Nng\Nnfocalpoint\ViewHelpers\FocalCropViewHelper

Arguments:

Argument Type Required Description
image object Yes The file reference object
width string No Width specification (append "c" for crop)
height string No Height specification (append "c" for crop)
cropVariant string No Crop variant for focal point lookup (default: default)
absolute bool No Force absolute URL (default: false)

Usage:

<!-- Crop to 400x200, focal point centered horizontally -->
<nnfp:focalCrop image="{image}" width="400c" height="200" />

<!-- Square crop -->
<nnfp:focalCrop image="{image}" width="200c" height="200c" />

<!-- With specific crop variant -->
<nnfp:focalCrop image="{image}" width="800c" height="600" cropVariant="mobile" />
Copied!

How it works:

The "c" suffix indicates that dimension should be cropped. The ViewHelper:

  1. Calculates the target aspect ratio (e.g., 400:200 = 2:1)
  2. Crops the original image to match that aspect ratio, centered on the focal point
  3. Scales the cropped result to the final dimensions

Example: For a 1200x800 image with focal point at (0.7, 0.3):

  • width="400c" height="200" creates a 2:1 crop (800x400 from original)
  • The crop window is positioned to keep the focal point (at x=840, y=240) as centered as possible
  • Result is scaled to 400x200

Output:

<img src="/fileadmin/_processed_/image_cropped.jpg" 
     width="400" height="200" />
Copied!

Replacing CropVariants with FocalCrop 

The nnfp:focalCrop ViewHelper can largely replace the need for multiple crop variants. Traditional TYPO3 crop variants require editors to manually define crop areas for each aspect ratio (desktop, tablet, mobile, square, etc.). This is time-consuming and error-prone.

With focal points, you can simplify this workflow:

  1. Use only the default crop variant in your TCA configuration
  2. Let editors set a single focal point on the important part of the image
  3. Use nnfp:focalCrop with different dimensions - the focal point stays centered automatically

Before (traditional approach):

<!-- Editor must define 4 different crop areas -->
<f:image image="{image}" cropVariant="desktop" width="1920c" height="600c" />
<f:image image="{image}" cropVariant="tablet" width="1024c" height="768c" />
<f:image image="{image}" cropVariant="mobile" width="768c" height="768c" />
<f:image image="{image}" cropVariant="square" width="400c" height="400c" />
Copied!

After (with focal point):

<!-- Editor sets ONE focal point, all crops are calculated automatically -->
<nnfp:focalCrop image="{image}" width="1920c" height="600" />  <!-- wide banner -->
<nnfp:focalCrop image="{image}" width="1024c" height="768" />  <!-- 4:3 -->
<nnfp:focalCrop image="{image}" width="768c" height="768" />   <!-- square -->
<nnfp:focalCrop image="{image}" width="400c" height="600" />   <!-- portrait -->
Copied!

Benefits:

  • Less editor work: One focal point instead of multiple crop definitions
  • Consistent results: The important subject stays visible in all aspect ratios
  • Flexible frontend: Developers can use any aspect ratio without backend changes
  • Simpler TCA: No need to configure multiple crop variants

Uri\FocalCropViewHelper (URI only) 

Returns only the URI of a focal-point-cropped image. Use this when you need the image URL for custom markup, srcset, or JavaScript.

Class: Nng\Nnfocalpoint\ViewHelpers\Uri\FocalCropViewHelper

Arguments: Same as FocalCropViewHelper

Usage:

<!-- In custom img tag -->
<img src="{nnfp:uri.focalCrop(image: image, width: '400c', height: '200')}" 
     alt="{image.alternative}" />

<!-- In srcset -->
<img src="{nnfp:uri.focalCrop(image: image, width: '400c', height: '200')}"
     srcset="{nnfp:uri.focalCrop(image: image, width: '400c', height: '200')} 1x,
             {nnfp:uri.focalCrop(image: image, width: '800c', height: '400')} 2x" />

<!-- As CSS background -->
<div style="background-image: url('{nnfp:uri.focalCrop(image: image, width: '1920c', height: '600')}')">
</div>

<!-- With absolute URL -->
{nnfp:uri.focalCrop(image: image, width: '400c', height: '200', absolute: 1)}
Copied!

Output:

/fileadmin/_processed_/csm_image_abc123.jpg
Copied!

BackgroundStyleViewHelper 

Returns a complete style attribute value for background images with focal point. Use this for divs or other elements with background images.

Class: Nng\Nnfocalpoint\ViewHelpers\BackgroundStyleViewHelper

Arguments:

Argument Type Required Description
image mixed Yes The file reference object or array
cropVariant string No The crop variant (default: default)
width string No Width for image processing (e.g., "1920" or "1920c")
height string No Height for image processing
backgroundSize string No CSS background-size value (default: cover)
includeRepeat bool No Include background-repeat: no-repeat (default: true)
fallbackPosition string No Fallback position if no focal point (default: 50% 50%)

Usage:

<!-- Simple usage -->
<div style="{nnfp:backgroundStyle(image: image)}">
    Content here
</div>

<!-- With image processing -->
<div class="hero" style="{nnfp:backgroundStyle(image: image, width: '1920')}">
    <h1>Hero Title</h1>
</div>

<!-- With crop variant -->
<div style="{nnfp:backgroundStyle(image: image, cropVariant: 'hero', width: '1920')}">
    Content
</div>

<!-- Custom background-size -->
<div style="{nnfp:backgroundStyle(image: image, backgroundSize: 'contain')}">
    Content
</div>
Copied!

Output:

<div style="background-image: url('/fileadmin/image.jpg'); background-size: cover; background-position: 50% 30%; background-repeat: no-repeat">
    Content here
</div>
Copied!

ObjectPositionViewHelper 

Returns a CSS object-position value based on the focal point coordinates.

Class: Nng\Nnfocalpoint\ViewHelpers\ObjectPositionViewHelper

Arguments:

Argument Type Required Description
image mixed Yes The file reference object or array
cropVariant string No The crop variant to get the focal point for (default: default)
fallback string No Fallback value if no focal point is set (default: 50% 50%)

Usage:

<img src="{image.publicUrl}"
     style="object-fit: cover; object-position: {nnfp:objectPosition(image: image, cropVariant: 'default')}" />
Copied!

Output:

<img src="/fileadmin/image.jpg"
     style="object-fit: cover; object-position: 50% 30%" />
Copied!

With fallback:

<img src="{image.publicUrl}"
     style="object-fit: cover; object-position: {nnfp:objectPosition(image: image, fallback: 'center top')}" />
Copied!

FocalPointViewHelper 

Returns the raw focal point data for a specific crop variant.

Class: Nng\Nnfocalpoint\ViewHelpers\FocalPointViewHelper

Arguments:

Argument Type Required Description
image mixed Yes The file reference object or array
cropVariant string No The crop variant to get the focal point for (default: default)
as string No Variable name to assign the focal point to

Usage (inline):

<f:debug>{nnfp:focalPoint(image: image, cropVariant: 'default')}</f:debug>
Copied!

Usage (with variable):

<nnfp:focalPoint image="{image}" cropVariant="default" as="fp">
    <f:if condition="{fp}">
        <p>Focal point: {fp.x} / {fp.y}</p>
        <div style="position: absolute; left: {fp.x * 100}%; top: {fp.y * 100}%;"></div>
    </f:if>
</nnfp:focalPoint>
Copied!

Return value:

Returns an array with x and y keys (values 0-1), or null if no focal point is set:

['x' => 0.5, 'y' => 0.3]
Copied!

Data Format 

Focal points are stored in the sys_file_reference.tx_nnfocalpoint_points field as a JSON string:

{
  "default": {"x": 0.5, "y": 0.3},
  "landscape": {"x": 0.9, "y": 0.712},
  "portrait": {"x": 0.2, "y": 0.5}
}
Copied!

Coordinate System:

  • x: 0 = left edge
  • x: 1 = right edge
  • y: 0 = top edge
  • y: 1 = bottom edge
  • x: 0.5, y: 0.5 = center of the image

Accessing Focal Points in PHP 

From FileReference Object 

use TYPO3\CMS\Core\Resource\FileReference;

/** @var FileReference $image */
$focalPointsJson = $image->getProperty('tx_nnfocalpoint_points');
$focalPoints = json_decode($focalPointsJson, true) ?: [];

// Get focal point for a specific variant
$defaultFocalPoint = $focalPoints['default'] ?? null;
if ($defaultFocalPoint) {
    $x = $defaultFocalPoint['x'];
    $y = $defaultFocalPoint['y'];
}
Copied!

From Extbase FileReference 

use TYPO3\CMS\Extbase\Domain\Model\FileReference;

/** @var FileReference $image */
$originalResource = $image->getOriginalResource();
$focalPointsJson = $originalResource->getProperty('tx_nnfocalpoint_points');
$focalPoints = json_decode($focalPointsJson, true) ?: [];
Copied!

Custom Integration Examples 

The following examples show how to use the focal point data in your templates. The result ensures that the important part of the image remains visible when the container is scaled or cropped.

Frontend result with focal point

Using object-position with the focal point coordinates keeps the subject visible regardless of container dimensions.

Required CSS 

For focal points to work correctly, you need CSS that allows the image to be positioned within its container. Here are the essential styles:

/* Basic focal point container */
.focal-container {
    position: relative;
    overflow: hidden;
}

/* Image with focal point - fills container and respects focal point */
.focal-container img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* object-position is set inline via ViewHelper */
}

/* Fixed aspect ratio containers */
.focal-container--16-9 {
    aspect-ratio: 16 / 9;
}

.focal-container--4-3 {
    aspect-ratio: 4 / 3;
}

.focal-container--1-1 {
    aspect-ratio: 1 / 1;
}

/* Background image with focal point */
.focal-bg {
    background-size: cover;
    background-repeat: no-repeat;
    /* background-position is set inline via ViewHelper */
}
Copied!

Example 1: Simple IMG Tag 

The most basic usage with a standard <img> tag:

<!-- Basic usage -->
<div class="focal-container" style="width: 300px; height: 200px;">
    <img src="{image.publicUrl}" 
         alt="{image.alternative}"
         style="object-fit: cover; object-position: {nnfp:objectPosition(image: image)}" />
</div>

<!-- With aspect ratio class -->
<div class="focal-container focal-container--16-9" style="width: 100%; max-width: 800px;">
    <img src="{image.publicUrl}" 
         alt="{image.alternative}"
         style="object-fit: cover; object-position: {nnfp:objectPosition(image: image, cropVariant: 'default')}" />
</div>
Copied!

Example 2: Using f:image ViewHelper 

Combine TYPO3's f:image ViewHelper with focal point positioning:

<!-- f:image with focal point -->
<div class="focal-container" style="width: 400px; height: 300px;">
    <f:image image="{image}" 
             alt="{image.alternative}"
             width="800" 
             style="object-fit: cover; object-position: {nnfp:objectPosition(image: image)}" />
</div>

<!-- f:image with crop variant -->
<div class="focal-container focal-container--4-3">
    <f:image image="{image}" 
             cropVariant="mobile"
             width="600c" 
             height="450c"
             style="object-fit: cover; object-position: {nnfp:objectPosition(image: image, cropVariant: 'mobile')}" />
</div>
Copied!

Example 3: Background Images 

Use focal points with CSS background images:

<!-- Simple background image -->
<div class="hero focal-bg" 
     style="height: 400px; 
            background-image: url('{image.publicUrl}'); 
            background-position: {nnfp:objectPosition(image: image)};">
    <h1>Hero Title</h1>
</div>

<!-- With f:uri.image for processed images -->
<div class="hero focal-bg" 
     style="height: 500px; 
            background-image: url('{f:uri.image(image: image, width: 1920)}'); 
            background-position: {nnfp:objectPosition(image: image, cropVariant: 'hero')};">
    <div class="hero__content">
        <h1>{headline}</h1>
        <p>{subheadline}</p>
    </div>
</div>
Copied!

CSS for the hero section:

.hero {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    text-align: center;
}

.hero::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
}

.hero__content {
    position: relative;
    z-index: 1;
    padding: 2rem;
}
Copied!

Example 4: Custom Logic with FocalPoint ViewHelper 

For advanced use cases, access the raw focal point data:

<nnfp:focalPoint image="{image}" cropVariant="default" as="fp">
    <f:if condition="{fp}">
        <f:then>
            <!-- Display focal point coordinates -->
            <p class="debug">Focal point: X={fp.x}, Y={fp.y}</p>
            
            <!-- Use in calculations -->
            <div class="image-with-hotspot" style="position: relative;">
                <img src="{image.publicUrl}" alt="" />
                
                <!-- Hotspot marker at focal point -->
                <span class="hotspot" 
                      style="left: {fp.x * 100}%; top: {fp.y * 100}%;">
                </span>
            </div>
            
            <!-- Conditional positioning based on focal point location -->
            <f:if condition="{fp.x} < 0.5">
                <f:then>
                    <div class="text-overlay text-overlay--right">
                        Text on right (focal point is on left)
                    </div>
                </f:then>
                <f:else>
                    <div class="text-overlay text-overlay--left">
                        Text on left (focal point is on right)
                    </div>
                </f:else>
            </f:if>
        </f:then>
        <f:else>
            <!-- Fallback when no focal point is set -->
            <img src="{image.publicUrl}" alt="" style="object-position: center center;" />
        </f:else>
    </f:if>
</nnfp:focalPoint>
Copied!

CSS for hotspot marker:

.image-with-hotspot {
    position: relative;
    display: inline-block;
}

.hotspot {
    position: absolute;
    width: 20px;
    height: 20px;
    background: rgba(255, 0, 0, 0.7);
    border: 2px solid white;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    animation: pulse 2s infinite;
}

@keyframes pulse {
    0%, 100% { transform: translate(-50%, -50%) scale(1); }
    50% { transform: translate(-50%, -50%) scale(1.2); }
}
Copied!

Example 5: Responsive Images with Picture Element 

Different focal points for different crop variants:

<picture>
    <!-- Mobile: square crop with mobile focal point -->
    <source media="(max-width: 767px)"
            srcset="{f:uri.image(image: image, cropVariant: 'mobile', width: '767c', height: '767c')}" />
    
    <!-- Tablet: 4:3 crop -->
    <source media="(max-width: 1023px)"
            srcset="{f:uri.image(image: image, cropVariant: 'tablet', width: '1023c', height: '767c')}" />
    
    <!-- Desktop: 16:9 crop -->
    <source media="(min-width: 1024px)"
            srcset="{f:uri.image(image: image, cropVariant: 'default', width: '1920c', height: '1080c')}" />
    
    <!-- Fallback image -->
    <img src="{f:uri.image(image: image, cropVariant: 'default', width: '1920c')}"
         alt="{image.alternative}"
         class="responsive-hero"
         style="object-fit: cover; object-position: {nnfp:objectPosition(image: image, cropVariant: 'default')}" />
</picture>
Copied!

Example 6: Data Attributes for JavaScript 

Pass focal point data to JavaScript for dynamic effects:

<nnfp:focalPoint image="{image}" cropVariant="default" as="fp">
    <img src="{image.publicUrl}"
         alt="{image.alternative}"
         class="js-parallax-image"
         data-focal-x="{fp.x}"
         data-focal-y="{fp.y}"
         data-has-focal-point="{f:if(condition: fp, then: 'true', else: 'false')}" />
</nnfp:focalPoint>
Copied!

Example JavaScript for parallax effect respecting focal point:

document.querySelectorAll('.js-parallax-image').forEach(img => {
    const focalX = parseFloat(img.dataset.focalX) || 0.5;
    const focalY = parseFloat(img.dataset.focalY) || 0.5;
    
    window.addEventListener('scroll', () => {
        const rect = img.getBoundingClientRect();
        const scrollProgress = 1 - (rect.bottom / (window.innerHeight + rect.height));
        
        // Parallax offset based on focal point
        const offsetY = (scrollProgress - 0.5) * 50 * (1 - focalY);
        img.style.objectPosition = `${focalX * 100}% calc(${focalY * 100}% + ${offsetY}px)`;
    });
});
Copied!

Extending the Extension 

Custom Form Element 

The focal point form element is registered in ext_localconf.php:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1702100000] = [
    'nodeName' => 'focalPointElement',
    'priority' => 40,
    'class' => \Nng\Nnfocalpoint\Form\Element\FocalPointElement::class,
];
Copied!

You can create a custom form element by extending FocalPointElement and registering it with a higher priority.

Custom ViewHelper 

Create custom ViewHelpers by extending the existing ones:

namespace YourVendor\YourExtension\ViewHelpers;

use Nng\Nnfocalpoint\ViewHelpers\ObjectPositionViewHelper;

class CustomObjectPositionViewHelper extends ObjectPositionViewHelper
{
    public function render(): string
    {
        $position = parent::render();
        // Add custom logic
        return $position;
    }
}
Copied!

API Reference 

FocalPointController 

Class: Nng\Nnfocalpoint\Controller\FocalPointController

Handles AJAX requests for the backend modal.

Methods:

wizardAction(ServerRequestInterface $request): ResponseInterface
Returns JSON data for the focal point wizard modal, including image URL, dimensions, crop variants, and existing focal points.
imageAction(ServerRequestInterface $request): ResponseInterface
Returns image data for a specific crop variant.

FocalPointElement 

Class: Nng\Nnfocalpoint\Form\Element\FocalPointElement

Custom FormEngine element that renders the focal point button and hidden input.

Extends: TYPO3\CMS\Backend\Form\Element\AbstractFormElement

ObjectPositionViewHelper 

Class: Nng\Nnfocalpoint\ViewHelpers\ObjectPositionViewHelper

Extends: TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper

Methods:

render(): string
Returns CSS object-position value (e.g., "50% 30%")

FocalPointViewHelper 

Class: Nng\Nnfocalpoint\ViewHelpers\FocalPointViewHelper

Extends: TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper

Methods:

render(): mixed
Returns focal point array or renders children with focal point variable

FocalCropViewHelper 

Class: Nng\Nnfocalpoint\ViewHelpers\FocalCropViewHelper

Extends: TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper

Methods:

render(): string
Returns <img> tag with server-side cropped image centered on focal point

Uri\FocalCropViewHelper 

Class: Nng\Nnfocalpoint\ViewHelpers\Uri\FocalCropViewHelper

Extends: TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper

Methods:

render(): string
Returns URI of focal-point-cropped processed image