Dedykowany widget Slider dla Elementora vs WPBakery

The author shares his experience and opinion about Elementor and WPBakery, highlighting the strengths and weaknesses of each plugin. While Elementor offers great flexibility for custom widgets, it has limitations such as no nested repeaters, while WPBakery provides an easy entry point for designs but does not have a free version, and both plugins suffer from excessive HTML DOM.

Dedykowany widget Slider dla Elementora vs WPBakery

W tym artykule stworzymy dedykowane widgety dla dwóch najpopularniejszych kreatorów stron: Elementor VS WPBakery.

Kod będzie uruchamiany w środowisku Docker z następującymi parametrami:

  • PHP 8.3
  • MariaDB 10.5
  • WordPress 6.6.2
  • Motyw potomny Twenty Twenty-Two
  • Composer + autoload klas
  • WPBakery 7.7.1 (najnowsza dostępna wersja)
  • Elementor 3.24.7 (najnowsza bezpłatna wersja w momencie pisania artykułu)
  • Query Monitor 3.16.4
  • Przeglądarka: Chrome na macOS (najnowsza wersja w momencie pisania artykułu)
  • Lighthouse (wersja mobilna i desktopowa, wyłącznie testy wydajności)

Wszystkie wtyczki wykorzystane podczas testów pochodzą z oficjalnych repozytoriów.

Kryteria porównania

  • Liczba ładowanych skryptów
  • Liczba ładowanych arkuszy stylów
  • Czas renderowania strony
  • Zużycie pamięci
  • Liczba zapytań do bazy danych
  • Czas odpowiedzi bazy danych
  • Liczba wolnych zapytań
  • Wynik Lighthouse dla urządzeń mobilnych
  • Wynik Lighthouse dla komputerów stacjonarnych

Tworzenie dedykowanego widgetu dla Elementora:

Zacznijmy od tworzenia dedykowanego widgetu dla Elementora.

Pierwszym krokiem jest inicjalizacja projektu za pomocą polecenia composer init i skonfigurowanie standardowych ustawień projektu. Jeśli nie znasz jeszcze Composera lub nie wiesz, jak korzystać z niego w swoich projektach, zajrzyj do tego artykułu.

Oto konfiguracja, którą ostatecznie otrzymałem:

{
  "name": "iwpdev/test-theme",
  "homepage": "https://i-wp-dev.com/",
  "autoload": {
    "psr-4": {
      "TestTheme\\": "src/php"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "TestTheme\\": "src/php"
    }
  },
  "prefer-stable": true,
  "authors": [
    {
      "name": "alexl",
      "email": "ad***@******ev.com"
    }
  ],
  "config": {
    "platform": {
      "php": "7.4"
    }
  },
  "minimum-stability": "dev",
  "require-dev": {
    "roave/security-advisories": "dev-latest",
    "squizlabs/php_codesniffer": "^3.5",
    "phpcompatibility/php-compatibility": "^9.3",
    "phpcompatibility/phpcompatibility-wp": "^2.1",
    "wp-coding-standards/wpcs": "^2.3",
    "php-coveralls/php-coveralls": "^2.4"
  },
  "require": {
  },
  "scripts": {
    "phpcs": "vendor/bin/phpcs --colors --standard=phpcs.xml"
  }
}

Drugim krokiem jest przejście do pliku functions.php i dodanie poniższego kodu. W tym kodzie wczytujemy style motywu nadrzędnego, następnie dołączamy Composera odpowiedzialnego za automatyczne ładowanie klas i inicjalizujemy naszą główną klasę.

<?php
/**
 * Functions theme file.
 *
 * @package iwpdev/test-theme
 */

use TestTheme\Main;

/**
 * Add parent style.
 *
 * @return void
 */
function enqueue_parent_styles(): void {
	wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css', '', '1.0', 'all' );
}

add_action( 'wp_enqueue_scripts', 'enqueue_parent_styles' );


require_once __DIR__ . '/vendor/autoload.php';

new Main();

Preferuję, aby kod w pliku functions.php był możliwie minimalistyczny, a cała logika znajdowała się w odpowiednio nazwanych, osobnych klasach, z których każda odpowiada za własną funkcjonalność.

Teraz czas utworzyć główną klasę motywu, która będzie odpowiadać za połączenie wszystkich klas oraz obsługę głównych funkcji naszego motywu.

<?php
/**
 * Main theme class.
 *
 * @package iwpdev/test-theme
 */

namespace TestTheme;

use Elementor\Plugin;

/**
 * Main class file.
 */
class Main {
	/**
	 * Main construct.
	 */
	public function __construct() {
		$this->init();
	}

	/**
	 * Init actions and filter.
	 *
	 * @return void
	 */
	public function init(): void {
		new WPBakerySwiperJsSlider();

		add_action( 'wp_enqueue_scripts', [ $this, 'add_style_and_scripts' ] );

		add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_elementor_widgets' ] );
	}

	/**
	 * Add style and script.
	 *
	 * @return void
	 */
	public function add_style_and_scripts(): void {
		wp_enqueue_style( 'test-theme-swiper', '//cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css', '', '11.0.0' );
		wp_enqueue_style( 'test-theme-main', get_stylesheet_directory_uri() . '/assets/css/main.css', [], '1.0.0' );

		wp_enqueue_script( 'test-theme-swiper', '//cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js', [ 'jquery' ], '11.0.0', true );
		wp_enqueue_script( 'test-theme-main', get_stylesheet_directory_uri() . '/assets/js/main.js', [ 'jquery' ], '1.0.0', true );

	}

	/**
	 * Register elementor widgets.
	 *
	 * @return void
	 */
	public function register_elementor_widgets(): void {
		Plugin::instance()->widgets_manager->register_widget_type( new ElementorSwiperJsSlider() );
	}
}

W tej klasie inicjalizujemy wszystkie akcje, filtry i klasy w funkcji init(). Nie sądzę, aby było potrzeba dodatkowo wyjaśniać ten kod.

Korzystamy z hooka elementor/widgets/widgets_registered, aby dodać nasz dedykowany widget do listy widgetów Elementora. Odpowiada za to funkcja register_elementor_widgets (jej kod znajduje się powyżej, w pliku Main.php).

Tworzymy klasę odpowiedzialną za nasz widget o nazwie ElementorSwiperJsSlider. Powinna ona rozszerzać standardową klasę Elementor\Widget_Base, co pozwoli nam utworzyć własny widget dla Elementora.

<?php
/**
 * Elementor swiper class.
 *
 * @package iwpdev/test-theme
 */

namespace TestTheme;

use Elementor\Controls_Manager;
use Elementor\Repeater;
use Elementor\Widget_Base;

/**
 * ElementorSwiperJsSlider class file.
 */
class ElementorSwiperJsSlider extends Widget_Base {
	/**
	 * Get Name Widget.
	 *
	 * @inheritDoc
	 */
	public function get_name() {
		return __( 'Swiper slider', 'twentytwentytwo' );
	}

	/**
	 * Get Title.
	 *
	 * @return string|void
	 */
	public function get_title() {
		return __( 'Swiper slider', 'twentytwentytwo' );
	}

	/**
	 * Get Icon Widget.
	 *
	 * @return string
	 */
	public function get_icon(): string {
		return 'eicon-hotspot';
	}

	/**
	 * Category Widget.
	 *
	 * @return string[]
	 */
	public function get_categories(): array {
		return [ 'basic' ];
	}

	/**
	 * Register controls.
	 */
	protected function register_controls(): void {
		$repeater = new Repeater();

		$this->start_controls_section(
			'content_app_slider',
			[
				'label' => __( 'Slider', 'twentytwentytwo' ),
				'tab'   => Controls_Manager::TAB_CONTENT,
			]
		);


		$repeater->add_control(
			'image',
			[
				'label'   => esc_html__( 'Add Images', 'twentytwentytwo' ),
				'type'    => Controls_Manager::GALLERY,
				'default' => [],
			]
		);

		$repeater->add_control(
			'title',
			[
				'label'       => __( 'Title', 'twentytwentytwo' ),
				'type'        => Controls_Manager::TEXT,
				'default'     => '',
				'placeholder' => __( 'Set a title', 'twentytwentytwo' ),
			]
		);

		$repeater->add_control(
			'sub_title',
			[
				'label'       => __( 'Sub Title', 'twentytwentytwo' ),
				'type'        => Controls_Manager::TEXT,
				'default'     => '',
				'placeholder' => __( 'Set a sub title', 'twentytwentytwo' ),
			]
		);

		$this->add_control(
			'app_slider',
			[
				'label'   => __( 'Navigation element', 'twentytwentytwo' ),
				'type'    => Controls_Manager::REPEATER,
				'fields'  => $repeater->get_controls(),
				'default' => [],
			]
		);

		$this->end_controls_section();
	}

	/**
	 * Output html render.
	 */
	protected function render(): void {

		$sliders = (object) $this->get_settings_for_display();
		?>
		<!-- Slider main container -->
		<div class="swiper <?php echo esc_attr( $css_class ?? '' ); ?>">
			<!-- Additional required wrapper -->
			<div class="swiper-wrapper">
				<!-- Slides -->
				<?php
				foreach ( $sliders->app_slider as $slide ) {
					$image = wp_get_attachment_image_url( $slide['image'][0]['id'], 'full' );
					?>
					<div class="swiper-slide" >
						<img
								src="<?php echo esc_url( $image ?? '' ); ?>"
								alt="<?php echo esc_attr( $slide['image'][0]['id'] ?? '' ); ?>">
                        <div class="swiper-text">
                            <h2><?php echo wp_kses_post( $slide['title'] ); ?></h2>
                            <h3>
                                <?php echo wp_kses_post( $slide['sub_title'] ); ?></h3>
                        </div>
					</div>
				<?php } ?>
			</div>
			<div class="swiper-button-next"></div>
			<div class="swiper-button-prev"></div>
			<div class="swiper-pagination"></div>
		</div>
		<?php
	}

}

Funkcja register_controls odpowiada za rejestrowanie pól naszego widgetu. Typy pól oraz przykłady kodu znajdziesz w dokumentacji pod tym adresem: https://developers.elementor.com/docs/editor-controls/control-types/

Funkcja render odpowiada za generowanie naszego widgetu Elementor. Jest ona wykonywana zarówno po stronie front-endu, jak i back-endu. Aby pobrać dane z pól, należy użyć funkcji $this->get_settings_for_display().

Następnie musimy utworzyć i dodać inicjalizację slidera oraz kilka stylów, aby wyglądał bardziej atrakcyjnie.

W folderze assets/js utwórz plik main.js. Będzie on odpowiadał za inicjalizację slidera.

/* global jQuery, Swiper */
jQuery( document ).ready( function( $ ) {
	const swiper = new Swiper( '.swiper', {
		pagination: {
			el: '.swiper-pagination',
			type: 'fraction',
		},
		navigation: {
			nextEl: '.swiper-button-next',
			prevEl: '.swiper-button-prev',
		},
	} );
} );

W folderze assets/css utwórz plik main.css. Będzie on odpowiadał za wygląd naszego slidera.

.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
	max-width: 1140px !important;
}

.swiper {
	width: 100%;
	container-type: inline-size;
}

.swiper-slide {
	text-align: center;
	font-size: 18px;
	background: #fff;
	display: flex;
	justify-content: center;
	align-items: center;
	position: relative;
	container-type: size;
}

.swiper-slide img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.swiper-wrapper {
	height: calc(100cqw / 1.77) !important;
}

.swiper-slide .swiper-text {
	position: absolute;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 20px;
	left: 0;
	right: 0;
	bottom: 0;
	top: 0;
	background: rgba(0, 0, 0, .5);
	padding: 10cqh 10cqw;
}

.swiper-pagination,
.swiper-button-next::after,
.swiper-button-prev::after {
	color: #fff !important;
}

.swiper-slide .swiper-text h1,
.swiper-slide .swiper-text h2,
.swiper-slide .swiper-text h3,
.swiper-slide .swiper-text h4,
.swiper-slide .swiper-text h5
.swiper-slide .swiper-text p {
	text-align: left;
	color: #fff;
	margin: 0;
	line-height: 1.1;
}

Jeśli wykonasz wszystkie kroki zgodnie z instrukcją, powinieneś uzyskać efekt przedstawiony na poniższych zrzutach ekranu.

Back-end Elementor widget
Fron-end Elementor widget

Nadszedł czas na pierwszy test. Aby eksperyment był miarodajny, wyłączono klasę inicjalizującą widget dla WPBakery oraz samą wtyczkę.

Oto wyniki, które uzyskałem:

  • Czas generowania strony: 0,3099 s
  • Zużycie pamięci: 7,6 MB
  • Czas wykonywania zapytań do bazy danych: 0,0247 s
  • Łączna liczba zapytań do bazy danych: 75
  • Wolne zapytania do bazy danych: 2
  • Skrypty: 30
  • Arkusze stylów: 23
  • Lighthouse – urządzenia mobilne: 61
  • Lighthouse – komputery stacjonarne: 93

Oto wyniki, które uzyskaliśmy. Należy pamiętać, że na stronie znajduje się tylko jeden slider i nie ma na niej żadnych dodatkowych bloków. Liczba wtyczek została ograniczona do minimum — aktywne są wyłącznie te wymienione powyżej, dlatego wyniki mogą się nieznacznie różnić.

Przejdźmy teraz do tworzenia slidera dla WPBakery.

Aby to zrobić, wystarczy odkomentować instancję klasy WPBakerySwiperJsSlider() w funkcji init w pliku main.php. Oto kod tej klasy:

<?php
/**
 * Wp Bakery Swiper slider.
 *
 * @package iwpdev/test-theme
 */

namespace TestTheme;

/**
 * WPBakerySwiperJsSlider class file.
 */
class WPBakerySwiperJsSlider {

	/**
	 * Icon folder url.
	 *
	 * @var string
	 */
	private string $icon_url = '';

	/**
	 * Swiper slider construct.
	 */
	public function __construct() {
		add_shortcode( 'wpb_swiper_slider', [ $this, 'output' ] );

		// Map shortcode to Visual Composer.
		if ( function_exists( 'vc_lean_map' ) ) {
			vc_lean_map( 'wpb_swiper_slider', [ $this, 'map' ] );
		}

		$this->icon_url = get_stylesheet_directory_uri() . '/assets/img/icons/';
	}

	/**
	 * Map field.
	 *
	 * @return array
	 */
	public function map(): array {
		return [
			'name'                    => esc_html__( 'WPBakery Swiper Slider', 'twentytwentytwo' ),
			'description'             => esc_html__( 'WPBakery Swiper Slider', 'twentytwentytwo' ),
			'base'                    => 'wpb_swiper_slider',
			'category'                => __( 'IWPDEV', 'twentytwentytwo' ),
			'show_settings_on_create' => false,
			'icon'                    => $this->icon_url . 'images-regular.svg',
			'params'                  => [
				[
					'type'       => 'param_group',
					'value'      => '',
					'heading'    => __( 'Slides', 'twentytwentytwo' ),
					'param_name' => 'slides',
					'params'     => [
						[
							'type'       => 'attach_image',
							'value'      => '',
							'heading'    => __( 'Slide', 'twentytwentytwo' ),
							'param_name' => 'slide_image',
						],
						[
							'type'       => 'textarea',
							'value'      => '',
							'heading'    => __( 'Title', 'twentytwentytwo' ),
							'param_name' => 'content_text_title',
						],
						[
							'type'       => 'textarea',
							'value'      => '',
							'heading'    => __( 'Sub Title', 'twentytwentytwo' ),
							'param_name' => 'content_text_sub_title',
						],
					],
				],
				[
					'type'       => 'css_editor',
					'heading'    => esc_html__( 'Custom css', 'twentytwentytwo' ),
					'param_name' => 'css',
					'group'      => esc_html__( 'Design options', 'twentytwentytwo' ),
				],
			],
		];
	}

	/**
	 * Output Short Code template
	 *
	 * @param mixed       $atts    Attributes.
	 * @param string|null $content Content.
	 *
	 * @return string
	 */
	public function output( $atts, string $content = null ): string {
		ob_start();
		$css_class = vc_shortcode_custom_css_class( $atts['css'] ?? '', ' ' );
		$slides    = vc_param_group_parse_atts( $atts['slides'] );

		if ( ! empty( $slides ) ) {
			?>
			<!-- Slider main container -->
			<div class="swiper <?php echo esc_attr( $css_class ?? '' ); ?>">
				<!-- Additional required wrapper -->
				<div class="swiper-wrapper">
					<!-- Slides -->
					<?php
					foreach ( $slides as $slide ) {
						$image = wp_get_attachment_image_url( $slide['slide_image'], 'full' );
						?>
						<div class="swiper-slide">
							<img
									src="<?php echo esc_url( $image ?? '' ); ?>"
									alt="<?php echo esc_attr( $slide['slide_image'] ?? '' ); ?>">
				            <div class="swiper-text">
                                <h2><?php echo wp_kses_post( $slide['content_text_title'] ); ?></h2>
                                <h3><?php echo wp_kses_post( $slide['content_text_sub_title'] ); ?></h3>
				            </div>
						</div>
					<?php } ?>
				</div>
				<div class="swiper-button-next"></div>
				<div class="swiper-button-prev"></div>
				<div class="swiper-pagination"></div>
			</div>
			<?php
		}

		return ob_get_clean();
	}
}

W tej klasie funkcja output odpowiada za generowanie naszego kodu HTML po stronie front-endu. Rejestruje również nowy shortcode w WordPressie, ponieważ WPBakery opiera się na architekturze shortcode’ów.

Funkcja map, przekazywana do funkcji WPBakery vc_lean_map (odpowiedzialnej za dodanie naszego widgetu w panelu administracyjnym WPBakery), odpowiada za dodawanie pól widgetu w panelu administracyjnym. Dokumentację dotyczącą typów pól i ich konfiguracji znajdziesz tutaj: https://kb.wpbakery.com/docs/inner-api/vc_map/

Jeśli wykonasz wszystkie kroki zgodnie z instrukcją, powinieneś uzyskać efekt przedstawiony na poniższych zrzutach ekranu.

Oto wyniki, które uzyskałem:

  • Czas generowania strony: 0,2421 s
  • Zużycie pamięci: 5,4 MB
  • Czas wykonywania zapytań do bazy danych: 0,0207 s
  • Łączna liczba zapytań do bazy danych: 33
  • Wolne zapytania do bazy danych: 0
  • Skrypty: 10
  • Arkusze stylów: 41 (Ten wynik jest niejednoznaczny, ponieważ zaczęły być ładowane skrypty wp-block z motywu nadrzędnego)
  • Lighthouse – urządzenia mobilne: 62
  • Lighthouse – komputery stacjonarne: 90

Porównajmy otrzymane wyniki:

Parametr porównaniaElementorWPBakery
Czas generowania strony–+
Zużycie pamięci–+
Czas wykonywania zapytań do bazy danych–+
Łączna liczba zapytań do bazy danych–+
Liczba wolnych zapytań do bazy danych–+
Skrypty–+
Arkusze stylów+–
Lighthouse – urządzenia mobilne–+
Lighthouse – komputery stacjonarne+–

Przemyślenia autora na temat każdej z wtyczek:

Elementor – bardzo lubię tworzyć dedykowane widgety dla tej wtyczki. Dobra dokumentacja oraz podejście obiektowe (OOP) pozwalają tworzyć elastyczne rozwiązania i rozbudowane widgety. Elementor sprawdzi się w przypadku klientów, którzy nie przywiązują dużej wagi do optymalizacji zapytań do bazy danych i liczby skryptów, ale cenią sobie możliwość podglądu efektów swojej pracy bezpośrednio w edytorze.

Wady tej wtyczki, z którymi spotkałem się w swojej pracy:

(To moje osobiste doświadczenia i inni użytkownicy mogą mieć inne doświadczenia z tą wtyczką. Autor nie jest ostatecznym autorytetem w tej kwestii, dlatego możesz nie zgadzać się z przedstawionym tutaj wnioskiem.)

  • Brak możliwości dodania pola repeatera wewnątrz innego repeatera (tak, ktoś może powiedzieć, że można utworzyć element wrappera i wykorzystać go w tym celu, ale nie jest to tak wygodne jak w przypadku konkurencyjnych rozwiązań).
  • Powtarzające się problemy po stronie front-endu w wersji mobilnej, ponieważ aby uzyskać oczekiwany efekt, muszę nadpisywać wiele klas.
  • Moim zdaniem nadmierne zagnieżdżanie elementów HTML DOM podczas budowania strony.

WPBakery – od dłuższego czasu korzystam z tej wtyczki do tworzenia dedykowanych motywów opartych na jej rozwiązaniach. Ma bardzo niski próg wejścia, jeśli chodzi o wdrażanie różnego rodzaju projektów i dedykowanych widgetów, oferuje świetną dokumentację, dużą i aktywną społeczność oraz jest łatwa w obsłudze.

Przejdźmy teraz do wad tej wtyczki:

  • Dostępna jest wyłącznie płatna wersja.
  • Możliwość użycia pola WYSIWYG tylko raz w obrębie elementu oraz brak możliwości dodania go w repeaterze.
  • Nadmierne zagnieżdżanie elementów HTML DOM, podobnie jak w przypadku Elementora.
  • Edytor wizualny w panelu administracyjnym jest niewygodny (czasami występują w nim błędy i konflikty z innymi wtyczkami; być może zostało to już naprawione, ale od dawna z niego nie korzystam). Korzystam wyłącznie z edytora back-endowego.

Pełny kod motywu znajdziesz na moim GitHubie tutaj.

Gotowy, aby wynieść swój projekt
na wyższy poziom?

Wprowadźmy Twoją wizję w życie dzięki profesjonalnemu tworzeniu stron i dedykowanym rozwiązaniom.
Skontaktuj się z nami już teraz i rozpocznijmy pracę nad przekształceniem Twoich pomysłów w rzeczywistość!

    Powiązane wpisy

    Wskazówki i spostrzeżenia z mojej drogi zawodowej

    Jak korzystać z autoloadingu Composera w WordPressie

    Jak korzystać z autoloadingu Composera w WordPressie

    • 02.10.2026
    • 14
    Przeniesienie strony ze środowiska lokalnego na serwer za pomocą dostępu SSH

    Przeniesienie strony ze środowiska lokalnego na serwer za pomocą dostępu SSH

    • 02.10.2026
    • 13
    Dlaczego WordPress 5.5.3 z PHP 8 zwraca błąd 404 na każdej stronie witryny?

    Dlaczego WordPress 5.5.3 z PHP 8 zwraca błąd 404 na każdej stronie witryny?

    • 02.10.2026
    • 13
    Wszystkie wpisy