Parallax with Gutenberg and React
< Back to BlogIn Web Design, parallax is almost as common of a buzzword as “light box” or “responsive design”. Because the effect is requested so frequently, I’ve baked a basic background image parallax pattern into my WordPress starting code. In this article, I’ll show you how I use React with Gutenberg to create my final result.
Quick rundown
For most WP projects, I build a theme and a collection of Gutenberg blocks. The blocks live in a plugin, and all of the files I edit for this technique will be inside that plugin.
The block
This technique is used in a general “box” block that I repurpose for most of my projects. Here is a working example:
If you’re familiar Gutenberg development, you know that most blocks have an edit and a save function. Edit represents the experience for the editor side, and the save renders the frontend.
If you like to use create-guten-block
, or another ready-to-go Gutenberg block development environment, just understand that a few of the steps in this process require the webpack config to be modified. Because I know it’s a commonly used tool, I’ve included instructions (where appropriate) for using this technique with that workflow.
The save function
Taking a look first at the save.js file, you might notice I’m writing with JSX and ES6. This is just out of personal preference, you can create blocks with ES5. I simply enjoy some of the ESNEXT features, which is why I like to write in this syntax.
At the top of the file, I import a component from WordPress that gets used later in the block, and a namespace
used throughout the plugin. I pass the block attributes and className
to the save function, destructure those attributes and use some of the values to create a couple of style properties.
In the return statement, I use the <section>
tag for the block’s parent element, where I assign the provided className
values from WordPress, and a few custom classes of my own.
For the background image, I have this weird <div>
within a <div>
thing setup here. Both of the elements are absolute positioned using a utility class that also sets the width and height to 100%. This causes both divs to match the parent element’s dimensions. Here is the css for that:
This is handy, because these styles apply to a few different elements, so it’s easier for me to bind them to a class, and simply attach it to each element that needs them.
Also I don’t assign the background image to the parent element for a few reasons:
- With this pattern, I can adjust the image opacity to create a decent contrast between the image and the block’s content.
- To properly create the parallax effect, I need a stand-alone element so its position can be bound to page scroll.
- Lastly, React will need an element to mount onto, once we get to that point in the process.
From there, the <InnerBlocks />
component is what is rendering the headings and paragraph. If you don’t know how InnerBlocks works, it allows you to render other blocks inside your blocks. If you’d like more information on how to use it, I have an article that goes over the most important properties for the component.
Finally, I wrap that component in a div, so I can set its position to relative and increase the z-index, ensuring it always displays on top of the background image element.
An important item to understand about Gutenberg: WordPress doesn’t allow any JavaScript from the save function to execute after the initial page render.
This is one of the main challenges that needs to be remedied to for the parallax effect to work. By mounting a React application on top of the rendered block’s html, I can provide extra functionality that allows the block’s structure to be manipulated. But before I get into that, let’s take a quick look at the edit function.
The edit function
Starting at the top, I import a variety of components from WordPress, and the same namespace I used in the save.js
file. This edit function is passed attributes
and className
, but also the setAttributes
function to control the block attribute values.
The attributes themselves, and the background properties also use the same pattern we saw in save. But it’s with the next 3 variables, chooseMedia
, removeMedia
, and onSelectMedia
where we finally run into some new code. These 3 functions are used by the <MediaUpload />
component to handle the image selection, as well as rendering the “add” and “remove” image buttons.
In the return statement, I render the <InspectorControls />
component and the block section element. InspectorControls is WordPress’ component for rendering UI in the editor’s sidebar. I’ve used this space to hold the MediaUpload component I was just mentioning, and a <RangeControl />
for the block’s background opacity.
From there, the block’s code is nearly the same. The only differences being the syntax for InnerBlocks.
Editing Existing Files
Now is the time to start those task runners. In my project, the command is npm run dev
, but for create-guten-block use npm run start
.
Create parallaxEnabled attribute
The first thing to do for this parallax functionality, is add a boolean attribute to track whether or not parallax is enabled. In attributes.js, I add “parallaxEnabled” with a type of “boolean” and set its default value to false.
Add attribute to edit.js
To control the attributes value, back in edit.js, I first destructure parallaxEnabled with the other attributes. ToggleControl
is perfect for handling boolean values, so I import it at the top, from the wp.components package. Under InspectorControls, between MediaUpload and RangeControl, and add the new component with its relevant props.
The label
prop renders a label for the user in the editor, while checked
displays the attributes value, and onChange
fires when the user interacts with the component.
Add attribute to save.js
Because React mounts onto the block’s html, we need a way to pass the blocks attributes to the application. This is easily done by rendering each block attribute value in an html data attribute. The three values needed to support this parallax experience are: the image object (bgImgObject
), image opacity (bgImgOpacity
) and the new parallaxEnabled
boolean.
First, parallaxEnabled is destructured with the other attributes. Then, each block attribute is added to a corresponding html data attribute, assigned to the block’s parent <section>
element. These values will be extracted later in the process and passed to a component using React’s render()
function.
For the image object to properly render in an html data attribute, it needs to first be parsed into a string. This is easily handled by wrapping the attribute in a function called JSON.stringify()
. For the other two, they can be placed just as they come, just be sure not to use camelCase for the html attribute names (they render lowercase regardless).
After that, we’re finished with save(), and ready to get into the exciting stuff.
Adding New Files
The next step is adding React to the project files. This is where the modified webpack configuration comes into use. But, before we get directly into that, let’s go through the code needed for frontend React support.
I add a react.js
, and BoxParallax.js
file to the Box block. Both of these files are concerned with the frontend, so they’re organized, along with the save.js
file, into a /save/
folder. React.js will check for Box blocks by querying a class, and BoxParallax.js is the component that will replace the existing background image html.
Create empty component in BoxParallax.js
I might as well quickly lay down some groundwork in this file and get it ready to properly configure.
I import React’s Component
class from the wp.element
package, instead of installing React into my project files. In this way, WordPress provides all of React’s functionality directly in the browser. This keeps the complied bundle size down, and allows Gutenberg developers to curate React’s version.
For now, I add a simple render function that returns an empty <div>
. I also make sure export the component, so other files can import and render it. We’ll come back to this file in the last bit of the process, but for now let’s look ahead to the block’s react.js file.
Configure react.js
For react.js itself, this one has a few different moving parts. The goal is, when WordPress renders a “Box” block, some React functionality needs to accompany it. I also won’t know how many of these blocks will be present on a page or post. Meaning, the code needs to be flexible enough to handle any number of “Box” blocks at a time.
To support these requirements, I use querySelectorAll()
to target the block’s class and assign the returned value to a boxBlocks
variable. The assembled class for this block is “wp-block-jab-box”, but I place the namespace value with template literals instead of a hardcoding “jab”.
QuerySelectorAll returns a nodelist, so I use forEach
to loop through boxBlocks, passing each block element with the arrow function syntax. Inside the forEach function, the html data attributes can be accessed from the block element using the dataset property.
Remembering that these data attributes are always lowercase, I’m careful not to use camelCase when they’re pulled out of the block element. Within the app itself, I do still want to refer to the block attributes consistently, so all of the properties in the new attributes object will use camelCase.
Also, each one of these values is a string, so they’ll need to be parsed back into their respective types. For the image, I’ll turn it back into a javascript object with JSON.parse()
, essentially un-doing JSON.stringify(). I want the background opacity to be an integer, so that value is wrapped with parseInt()
. Lastly, parallaxEnabled checking if its value is equal to a string of “true”, returning a proper boolean value.
Finally, it’s time to pass all of this data to the React app, and use those values inside the BoxParallax component. If you’ve worked with React before, you’ve probably used ReactDOM’s render
function to attach an application to an element on the webpage. As I stated earlier, React is provided to us with Gutenberg, so the render function comes from the wp.element
package.
Render takes two parameters, an element or component the application will render, and the DOM node React will use to mount onto. BoxParallax is passed to the first function parameter, with the attributes being passed using the ES6 spread operator (…). The second parameter, however, requires another quick step.
I want to mount onto the existing <div> I have setup for the block background, so I querySelector the <div> with a class of “block__react” directly from the current block element. Just to refresh your memory, this is the parent <div> from the goofy double-element syntax in save.js
. The blockMount
variable gets passed to the render function’s second parameter, resulting in:
Configuring webpack to Compile New Files
Very quickly, before we get into the webpack stuff, I need to add a top-level react.js file to the /src/ directory in the project. Webpack will use this file as it’s entry point, so each applicable block’s react file needs to be imported here. Since I’m only using one block at the moment, only one line of code is needed.
webpack Rundown
As previously stated, if you’re not using my project files to follow along, that’s totally cool. My processes are similar enough to create-guten-block, so I’ve included additional instructions for it. The first example will follow my pattern, but if you want to skip it, click here.
I setup my webpack configuration with a base file where most of the decisions get made, and then a dev and production routine that inherits most of the base config, but changes the output slightly to accommodate different purposes. The main differences between the two are: production minifies the code and prepares deployment ready files. The development routine avoids minification, but refreshes the browser every time I make a code change (hott releoading).
Taking a look at the base config, I import a collection of packages, and create a couple of variables used for compiling and extracting the project’s styles. Below that, in module.exports
, is where most of the change is going to take place.
Right now, I’ve configured a single entry point for blocks that compiles the primary block code and styles. The output property defines where webpack will place the files it compiles and creates, replacing the [name]
designation with the name of the entry property. Rules, then, determine how webpack treats each file type it is required to parse. I currently have rules setup for JS, SCSS, font files and image files.
The last item in here is the plugins property that has a collection of extra functionality I use to enhance the developer experience, and create a reusable environment. The first is a percentage progress bar for the compile process. The second, is a clears out the build folder when compilation occurs so that fresh files can be inserted into that folder. The last two are simply the extract configurations for the edit and save SCSS files.
webpack Changes
l start out by adding a new item to the entry object that points to the top level react.js file. Output is fine just as it is, but below it, I add a new property called externals
.
I’ve mentioned a couple of times already that WordPress provides React to us through Gutenberg. The problem is, webpack doesn’t understand this relationship. When it compiles code using React libraries, it will fail if React is not also installed and included in the code. To circumvent this, the externals property allows the compile process to ignore calls for assets I expect to be provided by another source, in this case: WordPress.
And that is it. Webpack should output browser ready javascript for both development
and production
build tasks. You will have to restart the build routine to see the changes take effect.
Configuring enqueue-assets
To get WordPress to request this new JS file, I open up enqueue-assets.php, where i’m already hooking into Gutenberg’s block_assets functions. enqueue_block_assets
requests files for both the front and backends, while enqueue_block_editor_assets
only pulls in files for the editor.
Obviously, I want React on the frontend, so l add it using enqueue_block_assets. But, I also want the experience in the editor to match as closely as possible. Because this function includes assets for both sides of the experience, this creates an opportunity to explore a succinct pattern to satisfy all placements. I’ll explain it in the next step, but first, I need to finish up with this file.
The setup is essentially the same as the main block.js file. I repurpose the $blocks_path
variable, changing its name and path to match the react file. I also copy the enqueue_script() function used for blocks.js, change its slug (jab-react-js
), path variable ($react_path
), and remove all but a single dependency: ['wp-element']
.
create-guten-block
If you need installation instructions, I refer you to the github. Once you’ve downloaded the package, installed its dependencies and created your first “guten-block” using react, these are the steps you’ll need to take to configure another JS file to compile along with blocks.js.
Eject the webpack configuration
In the same console you use to install dependencies, run the npm run eject
command. This will ask you some kind of “are you sure…?” message. Press “y” then enter to accept, and the configuration files will be created in a config folder.
Add react.js to paths.js
In config/paths.js
file, in the module.exports
object, add a new property for pluginReactJs
. Use the same syntax as pluginBlockJs for the property value, using resolvePlugin
to target 'src/react,js'
. Like this:
Add react to both dev and prod configs
In both webpack.config.dev.js
and webpack.config.prod.js
, the syntax for adding the new react entry property is the same. Look for the module.exports object, about halfway down each file, and add a new entry property, something like:
Enqueue react.build.js in init.php
Using their naming and syntax conventions, here is how the script would be registered with WordPress:
END create-guten-block
Configuring BoxParallax
So now that React is properly included, if I refresh the page, the background should disappear, because all BoxParallax is currently rendering, is an empty <div>. Very quickly, before I use the parallax components themselves, the block attributes need to be destructured from component props, and the background style properties replicated from save and edit.
What that means, though, is I’ve now configured these style properties three times in this project. That isn’t very “D.R.Y.” of me, and like I started to explain in the previous point, I want to look at pattern that removes this repetition.
In BoxParallax.js, I first destructure the block attributes being passed to the component. Then, I remove the <div> within .block__react
and bgOpacity
/ bgImage
variables from save.js, moving all of that that code to BoxParallax. The plan is to use parallaxEnabled
to decide what experience renders, because the prop will be provided directly to the component in react.js and edit.js (but not in save.js). This allows save() to render the static background experience it would have regardless, but enables the react pattern and edit function to disply the parallax effect.
Next, I quickly install a new npm package called react-scroll-parallax. The command is also flagged with –save so it’s added to the project’s package.json resulting in: npm i react-scroll-parallax --save
. Once that is done installing, I restart my build process and import two components from that package: Parallax
and ParallaxProvider
. Here is the final BoxParallax.js file, with the full breakdown just below it:
Parallax must be wrapped in a ParallaxProvider component. The documentation even suggests that you put the Provider component near the top of the application tree, but since I’m only going one component level deep, I think it’s safe to use it directly in BoxParallax.js. At the beginning of the return
statement then, I use the ternary operator with parallaxEnables
‘s boolean value to conditionally render the ParallaxProvider component or the static <div>.
From there, I add <Parallax />
as a child of <ParallaxProvider />
, and configure it to use the bgOpacity
and bgImage
style properties by passing them with styleInner
, Parallax’s proprietary prop. To determine the parallax movement direction, Parallax is equipped with a “y” prop (referring to the axis). This takes an array, the first position being the starting percentage and the second being the ending. A value that I think looks good is [0, -17]
. That means the <div> using the image in its background property will move down on the Y axis 17% of its total height, as user scrolls on the page.
Add <BoxParallax /> to save() and edit()
save.js
Back in save.js, I import BoxParallax and add it to .block__react to replace the inner <div> I moved to BoxParallax. In order to trigger the static background html, only the bgImgObject
and bgImgOpacity
attributes are passed, also eliminating the need for the bgImage / bgOpacity variables, so they are also removed.
edit.js
In this file, BoxParallax is imported in the same way, replacing the empty child <div> of .block__react. The style properties are again removed, but this time, the block attributes can be passed with the spread operator, just like react.js.
Final Tweaks
I’m nearly done, I promise. But, I need to tweak a few of my project’s styles to finish out this experience.
If you remember near the beginning of the article, I mentioned that I use a utility class to absolute position the elements used to display the background image. I’m bring this up again, because I need these styles to also apply to the <div>
s rendered by Parallax and ParallaxProvider, respectively.
While Parallax allows a className prop to be passed, it only allows the value to be applied to the parent element. This is problematic, because it requires me to retype these styles again in my code, which sucks (again D.R.Y.). Considering this is the most common way a parallax effect is utilized in my builds, the quicker and cleaner solution would be to simply include the parallax classes with the utility class.
Now, if we refresh, we should see that we have a slight problem. Since we’re moving the image along the Y axis, the element’s height needs to increase to compensate for this, otherwise a gap will be created between from the missing space.
You might think the solution would be to increase the element height to 117%, since that encompasses the extra 17% defined in the y={} prop. But, I actually have to overcompensate slightly because the amount of vertical movement is determined by the element’s height, so increasing that value also increases the translation amount proportionally.
To account for this tiny bit of extra height, I round up the height of .parallax-inner to 120%, giving it just enough extra slack to display properly. Here is my final result:
Outro
So, it might seem a little convoluted at first. But, once webpack is configured and your first block has some feature complete React functionality, adding this to other blocks in your plugin becomes much quicker and easier. Take a look at my code for the complete example. Hopefully this opens up some more possibilities for you with block development.