Using Free CSS3 Templates with React (Part 2)

In part one of this series I started the process of adapting html5up.net’s stylish Multiverse theme to React. I did this by altering the “recipe” provided by velocity360 in their two part series on Using Themes with React. The alterations were caused by my use of create-react-app and it’s automatic incorporation of webpack and babel. I felt the additional complexity was well worth the ability to spin up an app using the standard React stack.

Going from plain HTML to an attractive cat gallery.

In the end, the project resulted in a theme that looks like Multiverse, but did not act like Multiverse. The crucial transitions, blurs, and animations that helped to make the theme so appealing did not work.

I will implement those additional features in this post.

Design

I will step through each additional feature and implement it. My initial thoughts told me that there are two paths I could follow. For one, I could use the same libraries that Multiverse’s creator used. On the other hand, I could also use React specific animation to emulate the effects. I settled on demonstrating a combination of these techniques for the purposes of showing different options available. However, it might be more appropriate for a shipping product to use as few different techniqes as possible.

Using the same libraries requires knowing what each of the originally included libraries are for.

  • jQuery is a well known utility library, so it could be used for any number of things.
  • jQuery.poptrox “ Adds lightbox galleries to jQuery.”
  • Skel “…is a lightweight framework for building responsive sites and web apps.

Image Fade In

First, I decided to enable image fade in using FormidableLabs react-animations library. I want each ImageItem component to fade in when mounted. I also want the fade-in effect to be delayed for items further down the page. This delayed effect means that the duration of the fade in needs to vary on a component basis, and be part of the component’s state so I changed my previously presentational ImageItem component to full React Component Class instance. I also installed aphrodite to help me manipulate component CSS.

The delayed fade-in effect for each image is set by passing an additional prop to the ImageItem. This prop is equal to half the index value of each ImageItem. The higher the index, the longer the fade-in duration.

class ImageItem extends Component {
constructor(props) {
super(props);
this.state = {
styles: StyleSheet.create({
fadeIn: {
animationName: fadeIn,
animationDuration: props.duration + 's'
}
})
};
}
render() {
let imgSrc = this.props.src;
if (this.props.test) {
imgSrc = "images/fulls/01.jpg"
}
return (

<article className="thumb"
ref={(component) => this.myComponent = component}
onClick={this.props.onClick}>
<a className={"image " + css(this.state.styles.fadeIn)}
style={{
backgroundImage: `url(${imgSrc})`,
cursor: 'pointer',
}}>
</a>
<h2
style={{
fontFamily: "'Roboto', sans-serif"
}}>
{this.props.source_url}</h2>
</article>

)
}
}

As you can see, the image is immediately given the fadeIn class that includes fadeIn from react-animations. The blur filter is always present as a style; however, blur is set to 0 unless the component’s blur property is set to true. If the component’s blur property is set to true, react-motion interpolates the blur variable that is used in the component’s style.

Next, I animated the contact form that slides up when clicking the “About” button in the footer. An inspection of the original Multiverse example shows that the panel always has a class of panel.However, when the panel slides into focus, an additional class of active is added. I began by adding a variable called showContactPanelto the state of App.js to keep track of whether the panel should be shown. It is initialized as false, and clicking on the About button toggles this variable between true and false. I then passed this variable as a prop to the Footer component that I created in a separate file by cutting and pasting the footer div and its contents from App.js. The footer’s className is set to a state variable called className which is initialized as ‘panel’, the initial class of the footer. When the About button is clicked, and the prop changes from true to false (or vice versa) the Footer component receives a call to its componentWillReceiveProps method. Inside that method the Footer’s className state variable is changed to “panel active” if the Footer should be shown.

componentWillReceiveProps(nextProps) {
if ((nextProps.show)) {
this.setState({ className: 'panel active' });
}
else if ((!nextProps.show) ) {
this.setState({ className: 'panel' })
}
}

Additionally, if the Footer is shown, clicking on a picture should close the panel. I added an additional onClick property to my ImageItem component in App.js like so:

onClick={this.imageClicked.bind(this)}

In order for the click to register, I also had to add an onClick event to the ImageComponent so that the method in the onClick prop that was passed gets called.

onClick={this.props.onClick}

The imageClicked function in App.js checks to see if the contact panel is visible. If it is, the panel is toggled so that it is no longer shown.

// clicking on an image should ensure the contact panel is closed
imageClicked(){
if(this.state.showContactPanel){
this.toggleContactPanel();
}
}

Everything had been pretty straightforward so far. The next thing to do was to display one enlarged image in the center of the window when the user clicks on an image. I decided to build a new component (ImageModal) that would include both the frosted background effect and modal image, and to use the react-motion library for my animation requirements. React-motion interpolates values between a start and end point. These interpolated values can then be used as variables in a style object. For example the following code looks to see if the value of this.props.blur is true and interpolates from the 0 to 1 value if so. A false value interpolates from 1 to 0. The stiffness and damping values alter the effect.

blur: spring(this.props.blur ? 1 : 0, { stiffness: 170, damping: 17 }),

Unfortunately, I was only partially successful. No matter what I did, I was unable to create a frosted effect that would blur the gallery images. I was able to get the effect working in this JSfiddle, but the same code did not alter the images in the gallery.

I decided to be satisfied with blurring the individual gallery images whenever the modal picture was shown. The component returned by my ImageItem component is shown below:

<Motion
style={{
blur: spring(this.props.blur ? 1 : 0, { stiffness: 170, damping: 17 }),
}}>
{({ blur }) =>
<article className="thumb"
ref={(component) => this.myComponent = component}
onClick={this.props.onClick}>
<a className={"image " + css(this.state.styles.fadeIn)}
style={{
backgroundImage: `url(${imgSrc})`,
cursor: 'pointer',
filter: `blur(${blur * 5}px)`
}}>
</a>
<h2>{this.props.source_url}</h2>
</article>
}
</Motion>

this.props.blur is a boolean variable passed to the ImageItem by the App component, and is set to true whenever the ImageModal component is displayed.

I would appreciate it if anybody could tell me what prevented me from achieve my original goal of just using a single blurred div. Is there something I was missing? Please respond in the comments below.

Without and with blur

The ImageModal component has several requirements

  1. Display an image in the center of the screen
  2. Ensure the image fits inside the page
  3. The image expands vertically
  4. Clicking anywhere on the page closes the image
  5. Has controls to cycle through pictures and close the modal

Requirements 1 and 3 was filled by a div with the id of ‘imageModal’ given a fixed position and z-index of 100, and centering it’s contents. The top, bottom, left, and right CSS attributes were animated from 0% to 50% causing the div to grow from a size of 0% width and height to 100% width and height.

Requirement 2 proved to be somewhat tricky for larger images. Initially, I created a child of imageModal called pictureContainer This component scales from 0 to picture size in the Y direction to a maximum width or height of 75% of the imageModal parent. I first just put an image component inside this container. This technique worked for most images; however, tall images expanded vertically beyond the parent pictureContainer div. In the end, I included the hack from the accepted answer to this StackOverflow question. It involves setting the desired image as the background of the pictureContainer div. Unfortunately, pictureContainer does not know what size to be so an invisible copy of the same image is added as a child element to force pictureContainer to expand to the correct size.

Requirement 4 was satisfied with an onClick event that called a function in the App parent component. This function sets a showImageModal flag to false which animates the imageModal CSS top, bottom, left, and right attributes from 100% to 0% which cause the image to shrink.

I installed and used the react-icons package for requirement 5.

I also wanted the caption to fade in after the picture had scaled to its final width and height. React-motion provides an onRest property in the Motion component to call a specified function after an animation is completed. However, that callback function has to set some state in order to have subsequent animations fire. I created a boolean variable in the ImageModal component’s state specific to showing the caption. One gotcha that I ran into is that the caption would not animate even after I set state to show the caption. I had to implement the fix specified in this GitHub issue post to solve the problem.

Unfortunately, I could not duplicate the size transition that the Multiverse template uses when the user cycles images. The react-motion example of this technique requires knowing the width and height of the images in order to effect the transition. I decided to have the current image scale down to nothing and the next image scale up when the user changed images. This image swapping animation is also controlled by a boolean state variable observed and changed duringonRest. The code for this component can be found here.

Finally, all my changes meant that the page worked correctly without linking any of the scripts in index.html so I was able to remove those lines from my index.html file

Conclusion

This project has shown me that converting a theme to React is a balancing act. A balance must be found between using the theme’s original technologies and using React specific ones. For example, image fade-in animations were done using the react-animations library. On the other hand, the slide-up and slide-down effects for the footer were done by altering the class names attached to the component. Additionally, image blur was done using react-motion. Finally, It is even possible an entirely new component will need to be built in order to create the desired effect.

However, the end result of a well designed and stylish and responsive website may be worth the effort.

Reginald Johnson has maintained his passion for coding throughout his 20+ year career as an Officer in the United States Navy. He enjoys applying his training and experience in programming, Systems Engineering, and Operational Planning towards programming. Follow him Twitter @reginald3.