drupal

Matt Glaman: Why I've self-nominated for the Drupal Association At-Large Board Seat at this moment

I had been considering a self-nomination for the Drupal Association At-Large Board Seat a few times. This year, I decided to throw my hat into the ring. I've been a bit busy with baseball season kicking off (three kids, three teams đŸ« ) that I haven't written a blog yet, but I will, explaining why I've considered now the right time. 

Talking Drupal: Talking Drupal #505 - Custom Field Module

Today we are talking about the Custom Field Module, what it does, and why you might want to use it with guest Andy Marquis. We’ll also cover Facet Bot Blocker as our module of the week.

For show notes visit: https://www.talkingDrupal.com/505

Topics
  • Meet the Guest: Andy Marquee
  • Module of the Week: Facet Bot Blocker
  • Exploring the Custom Field Module
  • Benefits and Use Cases of Custom Field Module
  • Custom Field Module vs. Other Solutions
  • Advanced Features and Integrations
  • Reflecting on Past Projects and Key Value
  • Use Cases and Flexibility of Custom Fields
  • Advanced Features and Integrations
  • Challenges and Limitations
  • Future Roadmap and Improvements
Resources

Custom Field Module

Guests

Andy Marquis - drupal.org/project/custom_field apmsooner

Hosts

Nic Laflin - nLighteneddevelopment.com nicxvan John Picozzi - epam.com johnpicozzi Norah Medlin - tekNorah

MOTW Correspondent

Martin Anderson-Clutz - mandclu.com mandclu

  • Brief description:
    • Have you been looking for an inexpensive way to mitigate an upsurge of bot traffic on your Drupal site’s faceted search pages? There’s a module for that
  • Module name/project name:
  • Brief history
    • How old: created in Mar 2025, so about two months ago, by John Brandenburg (bburg) of Forum One
    • Versions available: 1.0.2 which support Drupal 10 and 11
  • Maintainership
    • Actively maintained
    • Security coverage
    • Number of open issues: 4 open issues, one of which is a bug, but it did have a fix merged 4 days ago
  • Usage stats:
    • 106 sites
  • Module features and usage
    • A number of sites are seeing a huge upswing in bot traffic, and quite often a big part of that bot traffic is on one or more pages with faceted search
    • Crawlers try to request every permutation of every possible combination of facets. If your page has multiple facets, and in particular facets that accept multiple values, the number of permutations becomes huge
    • Facet Bot Blocker works by allowing you to set a maximum number of facet requests to allow, what error to return, and a custom HTML message to return to blocked user agents
    • If you have Memcache or Redis available, it’s recommended to use the appropriate module, and Facet Bot Blocker will automatically store its settings there for reduced database reads
    • The project page is also clear that if you are able to use a true Web Application Firewall (like Cloudflare or Akamai), that would be a better strategy. But if you don’t have one available, Facet Bot Blocker can help to mitigate the surges in bot traffic that are causing problems for many Drupal sites, particularly those hosted on platforms that charge based on usage

A Drupal Couple: Running for the Drupal Association Board Again Because the Work Isn't Finished

Running for the Drupal Association Board Again Because the Work Isn't Finished

Image Image Image removed. Article body

IXP Initiative Co-Creator | Technical Account Manager | Community Contributor

 

I'm running for the Drupal Association Board again because the work we started isn't finished. When I first nominated myself in 2023, I talked about the need for better regional representation and sustainable talent pipelines. Today, as several major DA initiatives directly address these challenges, we need to ensure they work for everyone - from small regional agencies to large global companies, from new developers to experienced contributors. If you're a member of the Drupal community who values global inclusion and sustainable growth, I'm asking for your support in this important moment of transformation.

The Global Community Vision

I have worked for years within the Drupal community in both Latin America and the United States. I helped organize DrupalCon Latin America back in 2015, and honestly, I want to bring more opportunities like that to LATAM and other regions that get overlooked.

 

But this isn't just about Latin America. It's about recognizing that Drupal's future depends on communities everywhere having pathways to contribute and grow.

 

We're in a chicken and egg situation across many areas - community growth, code contribution, sustainability, and business development. For Drupal to grow at all levels of the ecosystem pyramid, we need more popularity and adoption. But this requires support from larger agencies. At the same time, the DA needs to develop sustainable revenue beyond just big agencies and DrupalCon North America. The challenge is that we still need those larger players to help create realistic pathways for regional agencies to participate and thrive. Breaking this cycle requires action on multiple fronts at once.

 

The International Federation initiative offers a way to break through some of these interconnected challenges by creating structures where global communities can actually participate, potentially moving all these areas forward together.

Building Solutions, Not Just Identifying Problems

The thing I'm most proud of is co-creating the IXP Initiative. We saw this fundamental problem - new developers couldn't get hired without experience, but couldn't get experience without being hired. So we built something to fix it. Working with other members of the community and the Drupal Association, we turned this idea into a real program. Now we have companies actually participating, offering structured pathways for new developers, and getting 250 contribution credits for each successful engagement.

 

This proves we can move beyond just talking about problems to building actual solutions that strengthen the entire ecosystem.

Why Regional Perspective Matters Now

My experience working in both Colombia and the United States since moving to the US in 2007 taught me how regional economic differences create both challenges and huge opportunities for Drupal.

 

At a recent board meeting, I brought up how a $1,000 partnership fee that works in the US becomes impossible in countries where monthly minimum wage is $200-300. These aren't just numbers - they represent talented developers who want to contribute but need different pathways to participation.

 

The International Federation initiative—a concept currently being explored to potentially give regional Drupal communities more formal representation—creates exactly the framework we need to address this. This structured approach can transform regional participation by providing clear business value to local companies, creating a more accessible path to becoming Drupal Certified partners while strengthening DA revenue.

Why The Timing Is Perfect

Several major initiatives are reshaping Drupal's global landscape:

1. Drupal CMS: Democratizing Enterprise Functionality

Drupal CMS now delivers enterprise-grade functionality to smaller markets and diverse budget contexts. With out-of-the-box features that previously required expensive custom development and no CS degree needed, Drupal has become accessible to communities previously excluded from its benefits.

2. International Federation: Creating Global Governance

The International Federation initiative is creating formal structures for global governance. With the working group starting in June 2025, we need board members who understand regional economic realities and can help shape this federation to truly serve diverse communities.

3. Marketplace Initiative: Enabling New Business Models

The Marketplace initiative is analyzing the feasibility of creating a marketplace for templates and quick site deployment options. I believe having quick ways to spin off a site that makes it 70% ready with Drupal CMS, decent price hosting to start, and templates that bring functionality and design very quickly, opens new paths to new business models. Add IXP to that and you have the affordable workforce to work on lower budget situations.

 

All these initiatives are interconnected: Drupal CMS makes participation technically possible, the International Federation creates the governance structure, and the Marketplace opens business opportunities. Making them work together is how we'll create a balanced ecosystem that serves both community values and business sustainability.

Balance Between Community and Business

I understand we need a sustainable Drupal Association that balances community needs with business realities. I recognize the importance of balancing community, open source, and businesses, but the reality is today's Drupal global activities sometimes lack on the business part.

 

We can do better. When I proposed bringing back DrupalCon Latin America, I suggested adapting the fair-style event model common in our region. Organizations pay for exhibition spots and actively invite end users, creating genuine business opportunities alongside community building.

 

Another example is how IXP-trained resources could enable agencies to open a Drupal CMS line of business for smaller budget projects. This creates opportunities to serve clients with limited resources - whether they're smaller companies, organizations in developing markets, or even departments within large enterprises looking for internal solutions. This approach simultaneously develops talent, creates business opportunities, and expands Drupal's reach.

 

I've been writing about ways to help make contribution more economically sustainable, including authentic community contribution approaches and ideas about how Drupal.org could better connect businesses with service providers across different economic regions.

What I'd Focus On

As a board member, I'd focus on:

 

  • Ensuring the International Federation serves all regions - by making regional economic realities central to governance structures
  • Creating opportunities in underrepresented regions - by expanding IXP's successful model to build global talent pipelines
  • Generating economic value for regional developers - by supporting DA initiatives like the Marketplace for sustainable growth
  • Creating opportunities for the entire global community - by connecting diverse economic contexts to build meaningful bridges

The Work Continues

I've spent years contributing to the Drupal community and creating programs that develop new talent. I've written extensively about community-first approaches that create sustainable business value. Now I want to bring that same focus to board-level decision making as these critical initiatives unfold.

 

The future of Drupal depends on communities everywhere having real pathways to contribute and grow. With the International Federation, Drupal CMS accessibility, and Marketplace opportunities all developing simultaneously, the board needs to ensure these initiatives work together to build a truly global community where everyone (regardless of region or economic context) can participate meaningfully.

 

If you believe in this vision for a truly global Drupal, please:

 

  1. Cast your vote during the election period (June 15-30, 2025)
  2. Share this nomination with your colleagues who care about global representation
  3. Reach out to me with questions or ideas at carlos.ospina@palcera.com

 

Together, we can build a Drupal ecosystem that works for everyone, everywhere.

 

– Carlos

Subject of IXP Graduates from Initiative to Program: Companies Can Start Using It Now! Beyond Makers and Takers: Being a Faker in Open Source About IXP Graduates from Initiative to Program: Companies Can Start Using It Now! Beyond Makers and Takers: Being a Faker in Open Source Rethinking Custom Modules: How ECA Blew My Mind Building the Bridge: How Drupal CMS and IXP Could Empower Digital Agencies Simplifying Drupal Updates: A Structured Approach to Worry-Free Maintenance Author Carlos Ospina Abstract oard nomination statement presenting Carlos Ospina's vision for global Drupal community representation, highlighting experience with International Federation development, cross-cultural business expertise, and sustainable talent pipeline creation through the IXP Initiative. Advocates for balanced community-business sustainability. Tags drupal-association-board global-representation international-federation ixp-initiative community-development drupal-leadership latin-america-drupal sustainable-growth Drupal Planet Rating Select ratingGive Running for the Drupal Association Board Again Because the Work Isn't Finished 1/5Give Running for the Drupal Association Board Again Because the Work Isn't Finished 2/5Give Running for the Drupal Association Board Again Because the Work Isn't Finished 3/5Give Running for the Drupal Association Board Again Because the Work Isn't Finished 4/5Give Running for the Drupal Association Board Again Because the Work Isn't Finished 5/5Cancel rating Average: 5 (1 vote) Leave this field blank

Add new comment

The Drop Times: The Elephant Doesn’t Know It’s an Elephant

Dear Readers,

Drupal isn’t struggling because it’s weak. It’s struggling because it doesn’t know its true strength. Like an elephant nudged around by smaller animals, it forgets that it can knock down walls if it just turns and pushes. We have spent years celebrating flexibility, adaptability, and a proud open-source ethos. But in trying to be everything to everyone, Drupal often ends up being invisible to those who need it most.

As Alejandro Moreno López points out, Drupal’s real power lies in what it enables without custom code. It is a low-code platform that hides in plain sight, quietly powering some of the most complex digital experiences across various industries. However, without sharp, targeted messaging and tools that speak the language of business problems, that power remains locked behind a technical perception. Most people do not explore Drupal because they do not see what it solves. They see a CMS, not a solution.

What if that changed? What if Drupal led with pre-built, industry-specific solutions? What if it made the first five minutes inspiring instead of intimidating? The pieces are already there. The potential is proven. What Drupal needs now is to step into its strength with clarity, coordination, and a message that leaves no room for doubt. When the elephant finally moves with purpose, it will no longer be ignored.

INTERVIEW

DISCOVER DRUPAL

EVENTS

ORGANIZATION NEWS

We acknowledge that there are more stories to share. However, due to selection constraints, we must pause further exploration for now.

To get timely updates, follow us on LinkedIn, Twitter and Facebook. You can also join us on Drupal Slack at #thedroptimes.

Thank you, 
Sincerely 
Alka Elizabeth 
Sub-editor, The DropTimes.

Salsa Digital: Unlocking AWS WofGA 3.0 discounts through Salsa

Image removed.Unlocking AWS WofGA 3.0 Discounts Through Salsa Digital: What Public Sector Agencies Need to Know The AWS / DTA Whole-of-Government Agreement (WofGA) 3.0 represents a major shift in how public sector agencies in Australia can procure and manage cloud infrastructure.  Signed in December 2024 and effective from May 2025, WofGA 3.0 is a three-year enterprise agreement negotiated between Amazon Web Services (AWS) and the Digital Transformation Agency (DTA). It allows eligible agencies—including federal, state, and local governments, higher education institutions, and government-owned corporations—to access significantly improved commercial terms and enterprise-grade services. What’s in the WofGA 3.0 Deal?

Four Kitchens: Creating depth and motion: A step-by-step guide to parallax

Image removed.

Mari NĂșñez

Frontend Engineer

Mari is a high-achieving Drupal frontend developer who has shown great proficiency in tackling complex frontend problems.

January 1, 1970

Crafting a visually engaging website isn’t just about eye-catching colors and typography — today it’s also about creating immersive experiences that captivate users as they scroll. One of the most compelling ways to achieve this is by using a parallax effect, where elements move at different speeds to create a sense of depth and motion.

With a thoughtful approach and some JavaScript, you can seamlessly add this effect to your site, enhancing storytelling and making your pages more dynamic.

This post will guide you through the process of integrating a custom parallax effect into your site. Whether building a feature-rich landing page or enhancing storytelling elements, this technique can bring your site to life. Let’s begin.

Building our component

Our example site was built in Drupal, but the overall concept would be the same in any CMS. First, we will need to build a component that has all the necessary fields that we want to display in our parallax. In this example, we will use Paragraph types and have two kinds of slides: one with an image and another without an image.

Parallax image slide

This slide will let us add an image, a title, and the caption or information we want to tell about that specific slide, the alignment of the information (left, center, or right), and an option if we want to hide the credit of the image or show it.

Image removed.
Parallax blank slide

This slide is similar to the previous one, but there are key differences. This one won’t include an image and anything else related to the image, and we allow a lot more text formatting on blank slides. This means that we can have the text on a blank slide take up much more of the available space without worrying about color contrast issues with advanced text formatting.

Image removed.

Once both paragraphs have been created, let’s create a ‘Parallax Slideshow’ paragraph that will only have a field that references the previous paragraphs created.

Image removed.

Connecting our component to the custom theme

Once our component is ready, the next step is to integrate it into our custom theme. In this example, we’re using Emulsify, our design system, as our custom theme, to ensure a consistent and modular approach to theming.

First, we will have our paragraph--parallax-slideshow.html.twig that will include a parallax-slideshow.twig, which has a JavaScript library called parallax-slideshow that is in charge of all logic to make our parallax effect work, and also some required styles.

{{ attach_library('your_theme/parallax-slideshow') }} {% set drupal = true %} {% include "@organisms/parallax-slideshow/parallax-slideshow.twig" with { 'slideshow_id': paragraph.id.0.value, 'slides': content.field_slide_items|render } %}

Here’s what our parallax-slideshow.twig looks like. Notice the empty <div class=””parallax-slideshow__image-wrapper””></div>. This is where the slide images will be rendered and where the fade-in and fade-out effects between images will occur.

{% set classes = [ paragraph.bundle|clean_class, "parallax-slideshow", ] %} <div{{ attributes.addClass(classes) }} data-id="{{ slideshow_id }}"> <div class="parallax-slideshow__wrapper"> <div class="parallax-slideshow__image-wrapper"></div> {{ slides }} </div> </div>

Then, we will have a paragraph--parallax-image-slide.html.twig and a paragraph--parallax-blank-slide.html.twig. Both files include a parallax-slide.twig, which is a molecule in the design system that organizes the content of each slide and adds all the needed styles. They are almost identical — the only difference being that the blank-slide will not pass the slide image to the parallax-slide.twig file.

{% include "@molecules/parallax-slide/parallax-slide.twig" with { 'slide_id': paragraph.id.0.value, 'slide_img': content.field_image|render, 'slide_title': paragraph.field_component_title.0.value, 'slide_caption': content.field_caption|render, 'slide_caption_alignment': paragraph.field_caption_alignment.0.value, 'slide_hide_credit': paragraph.field_hide_credit.0.value, 'slide_type': paragraph.type.0.value.target_id, } %}

Here’s what our parallax-slide.twig looks like:

{% set classes = [ 'parallax-slide', slide_caption_alignment ? 'parallax-slide--caption-' ~ slide_caption_alignment|lower : '', slide_type ? 'parallax-slide--' ~ slide_type|replace({'_': '-'}) : '', ] %} <div {{ attributes.addClass(classes) }} slide-data-id="{{ slide_id }}"> <div class="parallax-slide__info-wrapper"> <div class="parallax-slide__info-inner-wrapper full-width"> {% if slide_title %} <div class="parallax-slide__title-wrapper"> <h1 class="parallax-slide__title">{{ slide_title }}</h1> </div> {% endif %} {% if slide_caption %} <div class="parallax-slide__caption">{{ slide_caption }}</div> {% endif %} </div> </div> </div>

Preloading parallax slideshow data

To prevent a visible delay between slides, the component needs to preload the first two images on page load. As the user begins scrolling, additional images are loaded dynamically in the background. This ensures a seamless transition between slides without noticeable lag and enhances the overall user experience.

We need to pass structured data from the backend to JavaScript. Below is a function that loads the data and attaches it to drupalSettings for use in a theme.

function your_theme_preprocess_paragraph_parallax_slideshow(&$variables) { $paragraph = $variables['paragraph']; $pid = $paragraph->id(); $lazy_load_data[$pid] = []; if ($paragraph->hasField('field_slide_items')) { $slide_items_ref = $paragraph->get('field_slide_items'); $slide_items = $slide_items_ref->referencedEntities(); foreach ($slide_items as $slide_id => $slide) { // Initial setup of array. $lazy_load_data[$pid][$slide_id] = [ 'id' => NULL, 'image' => NULL, ]; // ID. if ($slide->hasField('id') && !$slide->get('id')->isEmpty()) { $lazy_load_data[$pid][$slide_id]['id'] = $slide->get('id')->first()->getValue(); } // Responsive image markup. if ($slide->hasField('field_image') && !$slide->get('field_image')->isEmpty()) { $lazy_load_data[$pid][$slide_id]['image'] = _your_theme_get_rendered_slide_image($slide); } } } // Attach to JS JSON object to read in theme. $variables['#attached']['drupalSettings']['yourTheme']['parallaxSlideshowData'] = $lazy_load_data; $variables['#attached']['library'][] = 'your_theme/parallax-slideshow'; } function your_theme_get_rendered_slide_image($slide) { if ($slide->hasField('field_image') && !$slide->get('field_image')->isEmpty()) { $image_view = $slide->field_image->view('default'); $rendered_image = \Drupal::service('renderer')->render($image_view); return $rendered_image; } return NULL; }

Once the data is attached to drupalSettings in our JavaScript file, we can access parallaxSlideshowData to dynamically load images and control the parallax effect.

JavaScript implementation of the parallax slideshow

Below is a breakdown of how the JavaScript file works to bring the parallax slideshow to life.

Drupal.behaviors.parallaxSlideshow = { attach: function (context) { const parallaxSlideshowData = drupalSettings.yourTheme.parallaxSlideshowData; if (!parallaxSlideshowData) return; const slideshows = once('parallax-slideshow', '.parallax-slideshow', context); slideshows.forEach((slideshow) => { const loadedSlideIds = new Set(); const loadedImages = new Set(); initializeParallaxSlideshow(slideshow, parallaxSlideshowData, loadedSlideIds, loadedImages); }); }, };

Let’s start by retrieving the slideshow data from drupalSettings and ensuring the script only runs once per slideshow element. The function initializeParallaxSlideshow is responsible for setting up and managing the parallax slideshow experience by initializing each slideshow. By tracking which slides have been loaded, we prevent redundant loading:

const slideshowDataID = slideshow.getAttribute('data-id'); const slideshowData = parallaxSlideshowData[slideshowDataID]; if (!slideshowData) return;

Then, it calls a preloadSlides function, which likely preloads images or other resources for the first two slides to prevent a visible delay between slides.

function preloadSlides(slideshowData, slideshow, loadedSlideIds, loadedImages){ slideshowData.slice(0, 2).forEach((slideData, index) => { // Check if the slide has already been added if (loadedSlideIds.has(slideData.id)) return; // Mark the slide as loaded loadedSlideIds.add(slideData.id); if (slideData.image !== null) { createImageDiv(slideData.id, slideData.image, slideshow, loadedImages, index === 0); } }); }

Next, it calls a createImageDiv helper function that is responsible for creating and managing an image element within the parallax slideshow.

function createImageDiv(slideID, slideImage, slideshow, loadedImages, firstImage = false) { const imgDiv = document.createElement('div'); imgDiv.className = 'parallax-slideshow__image'; imgDiv.innerHTML = slideImage; if (firstImage) { const image = imgDiv.querySelector('img'); image.addEventListener('load', () => { const slideshowOverlay = slideshow.querySelector( '.parallax-slideshow__overlay', ); const slideshowWrapper = slideshow.querySelector( '.parallax-slideshow__wrapper', ); if (slideshowOverlay) { slideshowOverlay.classList.add('fade-out'); setTimeout(() => { document.body.style.overflow = ''; slideshowWrapper.removeChild(slideshowOverlay); }, 1000); } }); } // Add a custom attribute for the slide ID imgDiv.setAttribute('data-slide-image-id', slideID); loadedImages.add({ id: slideID, image: imgDiv, }); }

The reason why we check if there is a firstImage is that we want the initial slide to fade in from black when it’s fully loaded. Once the image loads, it finds the overlay and the slideshow wrapper, fades out the overlay, removes the overlay, and re-enables scrolling.

Let’s go back to the initializeParallaxSlideshow. After the preloadSlides function there’s a scroll event listener for the parallax effect that listens for scroll events to update the slideshow’s image position dynamically.

The idea is to let the image wrapper take the whole height of the viewport, but since there can be components before or after the parallax slideshow, at some point it is necessary to change the position of the image wrapper, to let the user scroll and interact with other components.

window.addEventListener('scroll', () => { const windowHeight = window.innerHeight; const top = slideshow.getBoundingClientRect().top; const bottom = slideshow.getBoundingClientRect().bottom; const slideshowImageWrapper = slideshow.querySelector( '.parallax-slideshow__image-wrapper', ); if (top < 0 && bottom > windowHeight) { slideshowImageWrapper.style.position = 'fixed'; slideshowImageWrapper.style.top = 0; } else { slideshowImageWrapper.style.position = 'absolute'; if (windowHeight > bottom) { slideshowImageWrapper.style.top = 'unset'; slideshowImageWrapper.style.bottom = 0; } if (windowHeight < top) { slideshowImageWrapper.style.top = 0; slideshowImageWrapper.style.bottom = 'unset'; } } });

The following logic is to set a scroll hijacking if the parallax slideshow is the first component of the page and if the first slide is an image.

// Check if slideshow is within a parent of .content-top const isContentTopParent = slideshow.closest('.content-top') !== null; // Get the first slide and check if it has the class `parallax-slide--parallax-image-slide` const firstSlide = slideshow.querySelector('.parallax-slide'); const isFirstSlideParallaxImageSlide = firstSlide && firstSlide.classList.contains('parallax-slide--parallax-image-slide'); // Lock scroll if .content-top is present and the first slide is of type image if (isContentTopParent && isFirstSlideParallaxImageSlide) { const overlay = document.createElement('div'); overlay.className = 'parallax-slideshow__overlay'; slideshow .querySelector('.parallax-slideshow__wrapper') .appendChild(overlay); document.body.style.overflow = 'hidden'; }

Then, there’s a piece of code that iterates through all slides in the slideshow and calls the initializeSlideObserver() function on each slide.

const slides = slideshow.querySelectorAll('.parallax-slide'); slides.forEach((slide, index) => { const infoInnerWrapper = slide.querySelector( '.parallax-slide__info-inner-wrapper', ); initializeSlideObserver(slideshow, infoInnerWrapper, slide, slideshowData, loadedSlideIds, loadedImages); // Add classes if first image is an slide if (index === 0) { slide.classList.add('initial-slide'); if (isFirstSlideParallaxImageSlide) { slide.classList.add('initial-slide-image'); } } });

Now let’s take a look at the initializeSlideObserver() function — the one that is responsible for setting up an Intersection Observer to track when a slide enters the viewport and dynamically updates the slideshow’s displayed image accordingly. It ensures that the slideshow loads the next image only when needed, preventing unnecessary rendering and improving performance.

// Initialize Intersection Observer for Slides function initializeSlideObserver(slideshow, infoInnerWrapper, slide, slideshowData, loadedSlideIds, loadedImages) { // Watches when infoInnerWrapper enters or exits the viewport, // and triggers a callback whenever visibility changes const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { const slideshowWrapper = slideshow.querySelector( '.parallax-slideshow__wrapper', ); const slideshowImageWrapper = slideshowWrapper.querySelector( '.parallax-slideshow__image-wrapper', ); const slideID = slide.getAttribute('slide-data-id'); const slideImage = Array.from(loadedImages).find( (loadedImage) => loadedImage.id === slideID, ); const { isIntersecting } = entry; // Checks if the slide currently intersects the root if (isIntersecting) { const parent = slide.parentNode; const slides = Array.from( parent.querySelectorAll('.parallax-slide'), ); const index = slides.indexOf(slide); if (index !== 0) { // If not first slide, call the function to preload the next slide in advance. loadNextSlide(slideshowData, index, loadedSlideIds, loadedImages); } // Check if there is an existing image const previousImage = slideshowImageWrapper.querySelector( '.parallax-slideshow__image', ); if (slideImage) { slideImage.image.classList.add('fade-in'); slideshowImageWrapper.appendChild(slideImage.image); // If an existing image is found, remove fade-in class and remove it after a delay if (previousImage) { const previosImageID = previousImage.getAttribute( 'data-slide-image-id', ); if (previosImageID !== slideID) { setTimeout(() => { previousImage.classList.add('fade-out'); // Add fade-out class previousImage.classList.remove('fade-in'); // Remove fade-in class previousImage.classList.remove('fade-out'); // Remove fade-out class slideshowImageWrapper.removeChild(previousImage); }, 500); } } } else { if (previousImage) { const previosImageID = previousImage.getAttribute( 'data-slide-image-id', ); if (previosImageID !== slideID) { previousImage.classList.add('fade-out'); // Add fade-out class setTimeout(() => { previousImage.classList.remove('fade-out'); slideshowImageWrapper.removeChild(previousImage); }, 500); } } } } }); }, { // The callback triggers when at least 5% of infoInnerWrapper is visible. threshold: 0.05, }, ); observer.observe(infoInnerWrapper); }

Last but not least, there’s the loadNextSlide function that is responsible for preloading the next slide’s image to ensure a smooth transition when the user scrolls. This prevents unnecessary reloading of already loaded images. This function is very similar to the preloadSlides function.

function loadNextSlide(slideshowData, currentIndex, loadedSlideIds, loadedImages) { if (currentIndex + 1 < slideshowData.length) { const nextSlideData = slideshowData[currentIndex + 1]; // Check if the slide has already been added if (loadedSlideIds.has(nextSlideData.id)) return; // Mark the slide as loaded loadedSlideIds.add(nextSlideData.id); if (nextSlideData.image !== null) { createImageDiv(nextSlideData.id, nextSlideData.image, null, loadedImages); } } }

With these functions in place — handling image creation, slide observation, and preloading — you now have a dynamic and efficient parallax slideshow that seamlessly transitions between images as users scroll. By leveraging the Intersection Observer API, preloading logic, and smooth fade effects, the slideshow ensures a visually engaging experience without unnecessary performance overhead.

Once you’ve added the necessary styles to control positioning, animations, and transitions, your parallax slideshow should be fully functional across your site. This approach not only enhances the storytelling aspect of your content, but also keeps interactions smooth and lightweight.

Now, all that’s left is to fine-tune the visuals to match your design, and you’re set to create an immersive scrolling experience! Image removed.

The post Creating depth and motion: A step-by-step guide to parallax appeared first on Four Kitchens.

jofitz: Drupal AI: first steps

Artificial Intelligence has arrived in the Drupal ecosystem and it is already making huge waves. This is the first in a series of articles about my experiences as I dip a toe into these exciting waters.

The task

I was working on a large website with plenty of content dating back years, including numerous product reviews that required improved categorisation. The goal was to tag all of these review nodes with the relevant Make and Model.

The approach

I chose to write a custom Drush script to loop through all of the review nodes, using Artificial Intelligence to parse the Title and Body fields and return the make and model discussed therein.

public function reviewsMakeModel(): void { $fields = ['title', 'body']; foreach ($review_nodes as $review_node) { $data = []; foreach ($fields as $field) { $data[] = $review_node->get($field)->value; } $text = implode("\n", $data); $makeAndModel = $this->getMakeAndModel($text); // More to follow... } }

The provider

I...

Read more

The Drop Times: From Drupal Core to Static Site Innovation: Samuel Mortenson on Tome, SFC, and Open Source Legacy

Samuel Mortenson, longtime Drupal core contributor and creator of tools like Tome and SFC, reflects on nearly a decade of shaping the open-source CMS. In this in-depth interview, he shares insights on static site generation, developer experience, and why stepping back doesn’t always mean stepping away. If you're curious about the future of Drupal, static-first architecture, or open source sustainability, this is a must-read.