settings = $settings; // Tell WordPress to skip lazy loading for the first N images (WP 5.9+) $skip = (int) ($settings['lcp_skip_count'] ?? 2); add_filter('wp_omit_loading_attr_threshold', fn() => $skip); // For broader control (including non-WP images and iframes), filter content add_filter('the_content', [$this, 'process_content'], 25); add_filter('post_thumbnail_html', [$this, 'process_content'], 25); add_filter('widget_text', [$this, 'process_content'], 25); } /** * Process HTML content to add/manage lazy loading attributes. */ public function process_content(string $content): string { if (empty($content) || is_admin() || is_feed() || $this->is_amp_request()) { return $content; } $content = $this->process_images($content); if (!empty($this->settings['lazy_load_iframes'])) { $content = $this->process_iframes($content); } return $content; } /** * Add loading="lazy" to tags, skipping the first N for LCP. */ private function process_images(string $content): string { $skip = (int) ($this->settings['lcp_skip_count'] ?? 2); $exclude_classes = array_filter(array_map('trim', explode(',', $this->settings['exclude_classes'] ?? ''))); // The LCP budget is scoped to a single content block (the same // semantics as core's wp_omit_loading_attr_threshold): an // instance-wide counter shared across the_content, thumbnails, and // widgets counted tags in filter-execution order, not document // order, so the hero could still end up lazy-loaded. $position = 0; return preg_replace_callback('/]+>/i', function ($matches) use ($skip, $exclude_classes, &$position) { $tag = $matches[0]; // Already has a loading attribute - don't touch it if (preg_match('/\sloading\s*=/i', $tag)) { return $tag; } // Check excluded classes if (!empty($exclude_classes) && preg_match('/class=["\']([^"\']+)["\']/i', $tag, $class_match)) { $classes = explode(' ', $class_match[1]); foreach ($exclude_classes as $excluded) { if (in_array($excluded, $classes, true)) { return $tag; } } } // Only images this filter actually modifies consume the budget; // already-lazy and excluded tags above must not. $position++; // Skip first N images to protect LCP if ($position <= $skip) { return $this->inject_attribute($tag, 'loading', 'eager'); } // Add loading="lazy" and decoding="async" for below-fold images $tag = $this->inject_attribute($tag, 'loading', 'lazy'); $tag = $this->inject_attribute($tag, 'decoding', 'async'); return $tag; }, $content); } /** * Add loading="lazy" to