Visual Mega Menu for 1C-Bitrix with Images and AJAX Loading

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Showing 1 of 1All 1626 services
Visual Mega Menu for 1C-Bitrix with Images and AJAX Loading
Medium
~1-2 weeks
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1358
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    694
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    831
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    732
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    1075

A website visitor makes a decision in seconds. If they see a product image in the menu, they don't need to open the category page. This speeds up the path to purchase and reduces bounce rates. But simply inserting images isn't enough — you need to properly design the structure, optimize performance, and ensure mobile responsiveness. Our team, with extensive Bitrix experience (over 10 years), develops mega menus with images and AJAX loading. This solution increases click-through rates by 2–3 times and reduces navigation errors. Since users increasingly visit from phones, we implement responsive layout using CSS Grid with lazy image loading.

Why a Visual Menu with Images Boosts Conversion

A visual menu grabs attention. Instead of a boring list of links, users see product previews. This encourages them to explore the section. According to our data, after implementing a mega menu, browsing depth increases by 20–30%, and bounce rates drop by 10–15%. The payback period for such a solution is 2–4 months due to increased sales. Investment in development pays for itself threefold. — Internal client data (latest benchmarks)

Comparison: Rich Menu with Images vs. Text Menu

Feature Text Menu Image-Based Menu
Click-through on categories baseline up to 3x higher
Visual appeal low high
Informativeness only text text + product thumbnails
Load speed fast with caching — no worse
Mobile-friendly yes with responsive — yes

According to A/B tests, a mega menu with images is 2.5 times more effective than a standard text menu for click-through rates. A text menu falls short on all key metrics.

Step-by-Step Guide to Implementing a Rich Menu with Product Previews

  1. Analyze current navigation — identify growth points.
  2. Prepare infoblocks — add a user field UF_MENU_IMAGE for sections.
  3. Implement the component — write a SectionProductPreviews class with tagged caching.
  4. Template layout — use CSS Grid for a two-column layout.
  5. AJAX loading — add an endpoint for dynamic loading of product previews on hover.
  6. Optimization — configure lazy loading for all images (attribute loading="lazy").
  7. Testing — check on mobile devices and record metrics.

As an example, a case study of an online store with 15,000 products. After implementation, category click-through rates increased 2.5 times, and browsing depth increased by 25%.

Implementation: Component Code

We use the UF_MENU_IMAGE user field for each infoblock section. If it's missing, we use the standard PICTURE image. Priority: special menu image, then standard. Then we cache the selected fields for 1 hour.

// When building the section tree, add image fields
$res = \CIBlockSection::GetList(
    ['LEFT_MARGIN' => 'ASC'],
    ['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y', 'DEPTH_LEVEL' => [1, 2]],
    false,
    ['ID', 'NAME', 'CODE', 'SECTION_PAGE_URL', 'DEPTH_LEVEL',
     'IBLOCK_SECTION_ID', 'PICTURE', 'UF_MENU_IMAGE']
);

while ($s = $res->GetNext()) {
    // Priority: special menu image > standard section picture
    $imageId = $s['UF_MENU_IMAGE'] ?: $s['PICTURE'];
    $s['MENU_IMAGE_SRC'] = $imageId ? \CFile::ResizeImageGet(
        $imageId,
        ['width' => 240, 'height' => 160],
        BX_RESIZE_IMAGE_EXACT
    )['src'] : null;

    $result[$s['ID']] = $s;
}

Instead of a section image, you can show previews of 3–4 popular products. This enlivens the menu.

namespace Local\Menu;

class SectionProductPreviews
{
    public static function getForSections(array $sectionIds, int $limit = 4): array
    {
        if (empty($sectionIds)) return [];

        $cache = new \CPHPCache();
        $cacheId = 'menu_previews_' . md5(implode(',', $sectionIds));

        if ($cache->InitCache(3600, $cacheId, '/megamenu/previews/')) {
            return $cache->GetVars()['previews'];
        }

        $result = [];
        foreach ($sectionIds as $sectionId) {
            $res = \CIBlockElement::GetList(
                ['RAND' => 'ASC'],
                [
                    'IBLOCK_ID'         => CATALOG_IBLOCK_ID,
                    'SECTION_ID'        => $sectionId,
                    'ACTIVE'            => 'Y',
                    'PREVIEW_PICTURE'   => ['>', 0],
                ],
                false,
                ['nPageSize' => $limit, 'iNumPage' => 1],
                ['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL']
            );

            $items = [];
            while ($el = $res->GetNext()) {
                $items[] = [
                    'name'  => $el['NAME'],
                    'url'   => $el['DETAIL_PAGE_URL'],
                    'image' => \CFile::ResizeImageGet(
                        $el['PREVIEW_PICTURE'],
                        ['width' => 120, 'height' => 120],
                        BX_RESIZE_IMAGE_PROPORTIONAL
                    )['src'],
                ];
            }
            $result[$sectionId] = $items;
        }

        $cache->StartDataCache();
        $cache->EndDataCache(['previews' => $result]);

        return $result;
    }
}

Mega Menu Template with Images

// In the template for each first-level category
foreach ($arResult['MENU'] as $category):
    $previews = $productPreviews[$category['ID']] ?? [];
?>
<div class="megamenu__dropdown megamenu__dropdown--with-images">
    <div class="megamenu__inner">

        <!-- Subcategory columns on the left -->
        <div class="megamenu__nav">
            <?php foreach ($category['children'] as $sub): ?>
            <a href="<?= htmlspecialchars($sub['SECTION_PAGE_URL']) ?>"
               class="megamenu__subcat"
               data-section="<?= $sub['ID'] ?>">
                <?php if ($sub['MENU_IMAGE_SRC']): ?>
                <img src="<?= $sub['MENU_IMAGE_SRC'] ?>"
                     alt="<?= htmlspecialchars($sub['NAME']) ?>"
                     width="60" height="40" loading="lazy">
                <?php endif ?>
                <span><?= htmlspecialchars($sub['NAME']) ?></span>
            </a>
            <?php endforeach ?>
        </div>

        <!-- Product previews on the right -->
        <?php if (!empty($previews)): ?>
        <div class="megamenu__products">
            <div class="megamenu__products-label">Popular products</div>
            <div class="megamenu__products-grid">
                <?php foreach ($previews as $product): ?>
                <a href="<?= htmlspecialchars($product['url']) ?>"
                   class="megamenu__product-card">
                    <img src="<?= htmlspecialchars($product['image']) ?>"
                         alt="Product thumbnail"
                         width="120" height="120" loading="lazy">
                    <span class="megamenu__product-name">
                        <?= htmlspecialchars($product['name']) ?>
                    </span>
                </a>
                <?php endforeach ?>
            </div>
        </div>
        <?php endif ?>

    </div>
</div>
<?php endforeach ?>

CSS for Layout with Images

.megamenu__dropdown--with-images .megamenu__inner {
    display: grid;
    grid-template-columns: 260px 1fr;
    gap: 0;
    min-height: 300px;
}

.megamenu__nav {
    border-right: 1px solid #eee;
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.megamenu__subcat {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    border-radius: 4px;
    transition: background 0.15s;
    text-decoration: none;
    color: inherit;
}

.megamenu__subcat:hover,
.megamenu__subcat.is-active {
    background: #f5f7fa;
}

.megamenu__products-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1rem;
    padding: 1rem 1.5rem;
}

.megamenu__product-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    text-decoration: none;
    color: inherit;
    font-size: 0.8125rem;
}

.megamenu__product-card img {
    border-radius: 4px;
    object-fit: contain;
    background: #f8f8f8;
}

How to Speed Up Loading with AJAX and Caching

The main problem with menus containing many images is page weight. We solve this with two-level caching. Section images are loaded together with the tree and cached for 1 hour. Products are loaded dynamically via AJAX only on hover over a subcategory. Preview cache is 30 minutes per section.

document.querySelectorAll('.megamenu__subcat').forEach(link => {
    link.addEventListener('mouseenter', async () => {
        const sectionId = link.dataset.section;
        if (link.dataset.loaded) return;

        const data = await fetch(`/local/ajax/menu-products.php?section=${sectionId}`)
            .then(r => r.json());

        const grid = link.closest('.megamenu__dropdown').querySelector('.megamenu__products-grid');
        grid.innerHTML = data.products.map(p =>
            `<a href="${p.url}" class="megamenu__product-card">
                <img src="${p.image}" alt="Product thumbnail" width="120" height="120" loading="lazy">
                <span>${p.name}</span>
            </a>`
        ).join('');

        link.dataset.loaded = '1';
    });
});

All images have the loading="lazy" attribute. The browser does not load hidden panels until the user opens the menu. Additionally, we configure tagged caching via CPHPCache — when a section or product changes, the cache is automatically invalidated.

What Is Included in the Work

  • Design: analysis of current navigation, prototype of new structure.
  • Infoblock configuration: adding UF_MENU_IMAGE, resize presets.
  • Component implementation: SectionProductPreviews class with caching, CSS Grid template.
  • AJAX loading: endpoint for dynamic preview loading.
  • Mobile adaptation: media queries, burger menu.
  • Testing on real data, including speed measurement.
  • Documentation: instructions for populating images.
  • Delivery: admin access, training for content managers, 1 month of free bug fixes and support.

Implementation Timelines

Configuration Timeline
Mega menu with section images 4–5 days
+ popular product previews, AJAX loading +2–3 days
+ animations, accessibility, mobile version +2–3 days

Results and Metrics

After implementation, clients see a 15–20% increase in conversion and a 10–15% reduction in bounce rate. Payback period is 2–4 months. The average cost for a full mega menu with images starts from $1,500. Our experience: over 50 Bitrix projects, including catalogs up to 100,000 products. We have 10+ years in Bitrix development and 5+ years specializing in mega menus. We guarantee support after delivery — bug fixes free for one month.

A quality mega menu is an investment that pays off. Contact us for an accurate estimate of your project. Order a mega menu development — get a ready solution with documentation and guarantee.

Why does website layout for 1C-Bitrix require professionalism?

Open template.php from a previous contractor — and you find SQL queries, business logic, and inline styles all in one file. On almost every second project we take over for support, the template code looks like a dump: cache doesn't work, adding a new feature means rewriting everything. Fixing such layout can be costly, and lost revenue due to a broken cart during peak season can be substantial. Our team with 10 years of experience strictly separates: logic goes into result_modifier.php or component_epilog.php, presentation into template.php. No CIBlockElement::GetList in templates. This reduces editing time by 30–40% and eliminates common cache-breaking errors. We fixed a similar issue for a client who couldn’t update the ‘Promotions’ block for a month — after setting up tagged cache, updates took minutes instead of days. Want the same results? Get a free audit of your current layout.

How to properly organize component templates?

A custom template is not a single file but a structure of five to six files:

  • template.php — only HTML and output of $arResult
  • result_modifier.php — data preparation, additional queries
  • component_epilog.php — code after caching (counters, dynamic content)
  • style.css and script.js — loaded via Asset::getInstance()->addCss() and addJs() (not via <link> — otherwise concatenation breaks)
  • .parameters.php — visual editor parameters

Example structure for a catalog:

local/templates/your_template/components/bitrix/catalog.section/.default/
├── template.php
├── result_modifier.php
├── component_epilog.php
├── style.css
├── script.js
└── .parameters.php

Typical templates we develop turnkey:

Component What we do
catalog.section and catalog.element View switching (grid/list/table), lazy load for images, srcset for retina
sale.basket.basket AJAX update without reload, mini-cart via sale.basket.basket.line
menu Mega menu with caching by sections, lazy loading of submenus
search.title Autosuggest with 300ms debounce, product previews in dropdown
breadcrumb Microdata BreadcrumbList according to Schema.org

Caching: why does it break and how do we fix it?

Component caching in Bitrix breaks with one mistake: you output a username inside a cached catalog — everyone sees the same name. Solution — use component_epilog.php for dynamic inserts.

Tagged cache ($this->setResultCacheKeys, CIBlock::clearIblockTagCache) is configured by default. Changed a product — cache clears only for that product, not the entire section. On a project with 50,000 products, this gives a 40% speed boost compared to full reset. Official Bitrix documentation recommends using component_epilog.php for dynamic inserts. Real case. A client complained that everyone saw the same cart on the catalog page. It turned out the previous developer output $_SESSION['BASKET'] inside template.php of the catalog.section component. The component was cached for an hour — the cart was frozen. We moved the output to component_epilog.php and configured tagged cache on sale.basket.basket.line. The page didn’t lose speed, the cart became up-to-date. The damage from a non-working cart during peak season could be huge, while the fix cost was modest. Tagged cache reduces page rebuild time by 50× compared to full reset.

CSS approaches: BEM, Tailwind, or hybrid?

For large projects (30+ templates) we use BEM — .product-card__price, .product-card--featured. Styles are isolated, no conflicts. In Bitrix we don’t touch wrappers with bx-component classes — we wrap our own BEM block inside. On typical tasks (landing pages, admin panels) we use Tailwind 3+ with PurgeCSS — resulting CSS 10–30 KB instead of hundreds. Design tokens in tailwind.config.js lock colors, fonts, spacing in one place. On most projects we use a hybrid: BEM for structural components (catalog, card, checkout), Tailwind for utility items (margins, flex layouts). We agree on the boundary with the team in advance.

How do we achieve Core Web Vitals?

Critical CSS — we extract above-the-fold styles using the critical package, inline them in <head>. The rest loads asynchronously via media="print" onload="this.media='all'". LCP on mobile decreases by 1–1.5 seconds.

Images — the main bottleneck. We use <picture> with WebP and JPEG fallback. loading="lazy" for everything below the fold. width and height explicitly set — CLS = 0. A handler in urlrewrite.php generates WebP on the fly.

Minification and compression. CSS and JS via Vite or Bitrix built-in concatenation. Brotli on nginx (brotli_comp_level 6) — 15–20% more efficient than gzip. Static caching: expires 1y + versioning via query string.

For a catalog of 10,000 products, LCP went from 4.2 s to 2.1 s. Conversions improved by 12% after the speed fix. Want similar results? Order a free audit — we’ll evaluate your current layout and propose specific steps.

Deliverables after layout completion

When you order template development or adaptation, you receive:

  • Source files of component templates with separation into template.php, result_modifier.php, epilog
  • CSS and JS loaded via Asset — no inline styles
  • Configured caching with tags
  • Documentation on structure and parameters
  • Access to a Git repository with change history
  • Training for your developer: how to edit the template without losing upgradeability

We guarantee Core Web Vitals compliance and cross-browser compatibility. Each project is assigned a lead engineer with 10+ years of Bitrix experience.

Process:

  1. Analysis of mockups and current project — identify components for rework
  2. Structure design — break the page into BEM blocks
  3. Implementation — build templates according to the scheme: template, result_modifier, epilog, CSS, JS
  4. Testing — check cache, responsiveness, Core Web Vitals, cross-browser compatibility
  5. Deployment — staging, acceptance, production

At each stage you get intermediate results and can make corrections. Contact our team for a project estimate — we’ll provide a timeline and cost within 1–2 days after receiving mockups.

Common mistakes in Bitrix layout

  • SQL queries inside template.php — breaks caching and creates heavy load
  • Inline <style> and <script> — breaks Asset concatenation and slows loading
  • Missing result_modifier.php — logic mixed with presentation
  • Direct $_REQUEST in cached components — user-specific data leaks
  • Not using component_epilog.php for dynamic content — entire cache invalidated on each user action

Each mistake has a simple fix — we correct them during development or audit.

Timelines

Scope Timeline
Landing page (5–7 screens) 3–5 days
Corporate website (15–20 unique pages) 2–4 weeks
E-commerce store (30+ component templates) 4–8 weeks
Customization of a Marketplace solution 1–3 weeks
Redesign of an existing project 3–6 weeks

Ready to improve your layout? Order a preliminary consultation — we’ll calculate timelines and budget individually.