Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ The parent block acts as the controller and wrapper. It handles configuration, s
| `axis` | string | `'x'` | Carousel axis direction (`'x'` for horizontal, `'y'` for vertical). |
| `direction` | string | `'ltr'` | Carousel item direction: `'ltr'` (left-to-right) or `'rtl'` (right-to-left). |
| `slidesToScroll` | number | `1` | Number of slides to scroll per navigation action. |
| `lazyLoadImages` | boolean | `true` | Add `loading="lazy"` to images in slides. Slides can opt out via `disableLazyLoadImages`. |

---

### Child Block: `carousel-kit/carousel-slide`
The child block that serves as a container for individual slide content. Each slide can display custom blocks as defined by the parent's `allowedSlideBlocks` configuration.

#### Attributes

| Attribute | Type | Default | Description |
| :-------------------------- | :------ | :------------ | :------------------------------------------ |
| `disableLazyLoadImages` | boolean | `false` | Disable lazy loading for images in this slide (when carousel lazy loading is enabled). |

---

Expand Down
56 changes: 56 additions & 0 deletions inc/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
namespace Rt_Carousel;

use Rt_Carousel\Traits\Singleton;
use WP_Block;
use WP_HTML_Tag_Processor;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
Expand Down Expand Up @@ -41,6 +43,7 @@ protected function setup_hooks(): void {
add_action( 'init', [ $this, 'register_block_patterns' ] );
add_action( 'admin_notices', [ $this, 'legacy_plugin_notice' ] );
add_action( 'network_admin_notices', [ $this, 'legacy_plugin_notice' ] );
add_filter( 'render_block_rt-carousel/carousel', [ $this, 'handle_lazy_load_images' ], 16, 3 );
}

/**
Expand Down Expand Up @@ -260,4 +263,57 @@ private function load_patterns_from_disk(): array {

return $data;
}

/**
* Add loading="lazy" to images in carousel slides.
*
* @param string $block_content The block content.
* @param array $parsed_block The parsed block.
* @param \WP_Block $instance The block instance.
*
* @return string Modified block content.
*/
public function handle_lazy_load_images( string $block_content, array $parsed_block, WP_Block $instance ): string {
// $instance was added in WP 5.9.0, if it's not available, return the block content unmodified.
if ( ! $instance ) {
return $block_content;
}

// Bail early if the lazyLoadImages setting is not set.
if ( ! isset( $instance->attributes['lazyLoadImages'] ) ) {
return $block_content;
}

$lazy_load = (bool) $instance->attributes['lazyLoadImages'];

// If lazy loading is disabled, return as-is.
if ( ! $lazy_load ) {
return $block_content;
}

// Use WP_HTML_Tag_Processor to add loading="lazy" to <img> tags.
$processor = new WP_HTML_Tag_Processor( $block_content );
$slide_index = 0;

while ( $processor->next_tag( ) ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space after opening parathesis

$tag = $processor->get_tag();

// Keep a track of the slide index to determine if an image is in the first slide or subsequent slides.
if ( 'DIV' === $tag && $processor->has_class( 'embla__slide' ) ) {
$slide_index++;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use pre-increment instead: ++$slide_index.

}

// If it's the first slide, set loading="lazy". For subsequent slides, set loading="eager" and fetchpriority="high".
if ( 'IMG' === $tag && null === $processor->get_attribute( 'loading' ) ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use early exist

if ( 1 === $slide_index ) {
$processor->set_attribute( 'loading', 'eager' );
$processor->set_attribute( 'fetchpriority', 'high' );
} else {
$processor->set_attribute( 'loading', 'lazy' );
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor PHPCS note — there's a trailing blank line before the closing } of the while block that might trigger a sniff:

Suggestion:

while ( $processor->next_tag( 'img' ) ) {
    if ( null === $processor->get_attribute( 'loading' ) ) {
        $processor->set_attribute( 'loading', 'lazy' );
    }
}

return $processor->get_updated_html();
}
}
2 changes: 2 additions & 0 deletions src/blocks/carousel/__tests__/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe( 'CarouselAttributes Type', () => {
ariaLabel: 'Image carousel',
slideGap: 16,
slidesToScroll: '1',
lazyLoadImages: true,
};

expect( attributes ).toBeDefined();
Expand All @@ -58,6 +59,7 @@ describe( 'CarouselAttributes Type', () => {
ariaLabel: '',
slideGap: 0,
slidesToScroll: 'auto',
lazyLoadImages: false,
};

// Verify all keys exist
Expand Down
6 changes: 5 additions & 1 deletion src/blocks/carousel/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,14 @@
"slidesToScroll": {
"type": "string",
"default": "1"
},
"lazyLoadImages": {
"type": "boolean",
"default": true
}
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScriptModule": "file:./view.js"
}
}
10 changes: 10 additions & 0 deletions src/blocks/carousel/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default function Edit( {
autoplayStopOnMouseEnter,
ariaLabel,
slidesToScroll = '1',
lazyLoadImages,
} = attributes;

const [ emblaApi, setEmblaApi ] = useState<EmblaCarouselType | undefined>();
Expand Down Expand Up @@ -241,6 +242,15 @@ export default function Edit( {
onChange={ ( value ) => setAttributes( { dragFree: value } ) }
help={ __( 'Enables momentum scrolling.', 'rt-carousel' ) }
/>
<ToggleControl
label={ __( 'Lazy Load Images', 'carousel-kit' ) }
checked={ lazyLoadImages }
onChange={ ( value ) => setAttributes( { lazyLoadImages: value } ) }
help={ __(
'Load images only when they enter the viewport.',
'carousel-kit',
) }
/>
<SelectControl
label={ __( 'Alignment', 'rt-carousel' ) }
value={ carouselAlign }
Expand Down
6 changes: 5 additions & 1 deletion src/blocks/carousel/slide/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
"attributes": {
"verticalAlignment": {
"type": "string"
},
"disableLazyLoadImages": {
"type": "boolean",
"default": false
}
},
"usesContext": [
"rt-carousel/carousel/allowedSlideBlocks"
],
"editorScript": "file:./index.js"
}
}
2 changes: 2 additions & 0 deletions src/blocks/carousel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export type CarouselAttributes = {
ariaLabel: string;
slideGap: number;
slidesToScroll: string;
lazyLoadImages: boolean;
};

export type CarouselViewportAttributes = Record<string, never>;
export type CarouselSlideAttributes = {
verticalAlignment?: BlockVerticalAlignmentToolbar.Value;
disableLazyLoadImages?: boolean;
};
export type CarouselControlsAttributes = Record<string, never>;
export type CarouselDotsAttributes = Record<string, never>;
Expand Down
Loading