Creating Interactive Product Pages With React and Cloudinary

Because today’s consumers expect a level of product customization they purchase online, e-commerce sites must support more personalization, a key to which is adding features to product pages, also called order pages. That’s where shoppers can customize the products they want to buy by changing product properties, such as size, color, delivery means, and quantity.

User-friendly UX demands that when shoppers make purchases, they receive visual feedback. For example, if someone is buying a shirt in red, the product page should account for that by updating the shirt’s image to a red variant. Given how difficult it is to change the color of non-SVG images, implementing such a feature can be daunting. Fortunately, Cloudinary, a cloud-based solution for managing and delivering rich media, including images and videos, makes it a breeze.

This post steps you through the process of leveraging Cloudinary to build a demo app with these three capabilities for product pages:

  • Varying image sizes: Cloudinary can seamlessly deliver product images in multiple sizes (main image; thumbnails; hi-resolution, zoomed-in images). All you need to do is add the sizes to the URL, after which Cloudinary dynamically generates the various images.
  • Varying colors: Some products come in multiple colors, the images for which are typically individual ones that all need to be uploaded and processed. With Cloudinary, you can change the color of a product by calculating how much to adjust the RGB channels of the original color to arrive at the desired color. This approach allows infinite scaling, enabling you to switch to any color in the spectrum.
  • Custom text: Many retailers offer features for personalization, such as embroidering, adding logos, and designing your own products. Cloudinary can overlay text and images on top of a shirt, for example, and can even make it look photorealistic with displacement mapping.

This is what the demo app looks like:

Here are the topics:

  • Setting Up the Environment
  • Solving Problem 1: Varying Image Sizes
  • Solving Problem 2: Varying Colors
  • Solving Problem 3: Custom Text
  • Trying It Out

Setting Up the Environment

Cloudinary integrates well with all front-end frameworks so feel feel to use the JavaScript core library or a specific framework-wrapper library. For the demo app in this article, set up the environment with React, as follows:

# 1. Install create-react-app globally.
npm install -g create-react-app
# 2. Create a new app.
create-react-app cloudinary-retail-page
# 3. Install the Cloudinary React library.
npm install --save cloudinary-react

For simplicity, all the sample code resides in the src/App.js folder.

Solving Problem 1: Varying Image Sizes

Above are two sets of images: main and thumbs (short for thumbnails). The main image is for shoppers; the thumbs, for interactions. Obviously, the selected thumb is the same as the main image, and you can deliver them in either of these two most common ways:

  • Manually create multiple images for a given product. However, this approach is not scalable given the large amount of products on most e-commerce sites.
  • Resize high-resolution images to fit the main or thumb section by means of CSS width and height properties. That’s the wrong thing to do because if an image has three variations and if each of them takes up 800 KB, you end up with the following: 1 main (800 KB) + (1 thumb (800 KB) x 3 models) = 3,200 KB (3.2 MB)

By specifying the size you prefer during delivery through image transformation with Cloudinary, you would have only one image on your Cloudinary server and can request a particular size for delivery. See this example code:

import React, { Component } from 'react';
import {Image, CloudinaryContext, Transformation} from 'cloudinary-react';

const ImageTransformations = ({width, selectedShirt}) => {
    return (
        <Image publicId={selectedShirt.main+'.jpg'}>
            <Transformation width={width} crop="scale" />
        </Image>
    );
};

class App extends Component {

    constructor(props) {
        super(props);
        const defaultShirt = {id: 1, main: 'shirt_only'};
        this.state = {
            shirts: [
                defaultShirt,
                {id: 2, main: 'laying-shirt'},
                {id: 3, main: 'hanging_t-shirt'}
            ],
            selectedShirt: defaultShirt,
        };
    }

    selectShirt(thumb) {
        this.setState({selectedShirt: thumb}, _ => this.forceUpdate())
    }

    render() {

        return (
          <div className="App">
              <CloudinaryContext cloudName="<YOUR_CLOUD_NAME>">
                  <div id="imageDemoContainer">
                      <div id="mainImage">
                          <ImageTransformations
                              width="600"
                              rgb={rgb}
                              selectedShirt={this.state.selectedShirt}
                              text={this.state.text} />
                      </div>
                      <div id="imageThumbs">
                          <ul id="thumbs">
                              {this.state.shirts.map(thumb => {
                                 return (
                                 <li className={thumb.main === this.state.selectedShirt.main ? 'active': ''} onClick={this.selectShirt.bind(this, thumb)} key={thumb.id}>
                                     {/*<Image publicId={thumb.main}>*/}
                                         {/*<Transformation width="75" crop="scale" />*/}
                                     {/*</Image>*/}
                                     <ImageTransformations
                                         width="75"
                                         rgb={rgb}
                                         selectedShirt={thumb}
                                         text={' '} />
                                 </li>
                                 )
                              })}
                          </ul>
                      </div>
                  </div>
              </CloudinaryContext>
          </div>
        );
    }
}

export default App;

Cloudinary’s React library exposes four components:

  • Image: Delivery of images, each with a public ID (publicId).
  • Video: Delivery of videos, each with a public ID (publicId).
  • Transformation: Transformations of images and videos.
  • CloudinaryContext: Wrapping of multiple instances of Image and Video under your cloud name, which Cloudinary assigns to you once you’ve signed up for free.

Here’s what transpires:

  1. The React state holds an array of image public IDs, `shirts`, that are on the Cloudinary server.
  2. You iterate over that array of shirts and request images of width 75 px. with the custom `ImageTransformations` component. Those images are then displayed as thumbs.
  3. On a click of a thumb, `ImageTransformations` renders the main image of width 600 px., which makes for an optimized solution, as shown here:

1 main (800 KB) + 1 thumb (100 KB ) x 3 models = 1,100 KB (1.1 MB)

3,200 KB  – 1,100 KB = 2,100 KB (2.1 MB)

That’s a saving of more than 60% of extra kilobytes, thanks to Cloudinary.

Solving Problem 2: Varying Colors

When shoppers choose a color for a product, what retailers usually do is create a clickable color palette and replace the images with the product image that sports the selected color. Such a practice does not scale, however, because a shopper might prefer a color that does not match any of the product colors on your page. Ideally, a given product would have countless colors, or shoppers would be able to customize the product’s color after purchase.

With Cloudinary, you can adjust the RGB channels of the original color with simple transformation—and with only one image as the source. See this code:

<Transformation effect="red:255" />
<Transformation effect="blue:255" />
<Transformation effect="green:255" />

You can then apply that code to the previous example, like this:

...
import { SketchPicker } from 'react-color';

const ImageTransformations = ({width, rgb, selectedShirt, text}) => {
    return (
        <Image publicId={selectedShirt.main+'.jpg'}>
            <Transformation width={width} crop="scale" />
            <Transformation effect={'red:'+((-1+rgb.r/255)*100).toFixed(0)} />
            <Transformation effect={'blue:'+((-1+rgb.b/255)*100).toFixed(0)} />
            <Transformation effect={'green:'+((-1+rgb.g/255)*100).toFixed(0)} />
            <Transformation underlay={selectedShirt.underlay}  flags="relative" width="1.0" />
            <Transformation overlay={selectedShirt.overlay}  flags="relative" width="1.0"  />
        </Image>
    );
};

class App extends Component {

    constructor(props) {
        super(props);
        const defaultShirt = {id: 1, main: 'shirt_only', underlay: 'model2', overlay: ''};
        this.state = {
            shirts: [
                defaultShirt,
                {id: 2, main: 'laying-shirt', underlay: '', overlay: ''},
                {id: 3, main: 'hanging_t-shirt', underlay: '', overlay: 'hanger'}
            ],
            text: ' ',
            selectedShirt: defaultShirt,
            background: {rgb:{r:255,g:255,b:255}}
        };
    }

    handleColorChange(color) {
        // Updates color
        this.setState({ background: color }, _ => this.forceUpdate());
    };

    selectShirt(thumb) {
        // Updates main image
        this.setState({selectedShirt: thumb}, _ => this.forceUpdate())
    }

    render() {
        const rgb = this.state.background.rgb;

        return (
          <div className="App">
              <CloudinaryContext cloudName="christekh">
                 <div id="demoContainer">
                      <div id="header">
                          <a href="http://cloudinary.com/">
                              <img width="172" height="38" src="http://res-1.cloudinary.com/cloudinary/image/asset/dpr_2.0/logo-e0df892053afd966cc0bfe047ba93ca4.png" alt="Cloudinary Logo" />
                          </a>
                          <h1>Product Personalization Demo</h1>
                      </div>
                  </div>
                  <div id="imageDemoContainer">
                      <div id="mainImage">
                          <ImageTransformations
                              width="600"
                              rgb={rgb}
                              selectedShirt={this.state.selectedShirt}
                              text={this.state.text} />
                      </div>
                      <div id="imageThumbs">
                          <ul id="thumbs">
                              {this.state.shirts.map(thumb => {
                                 return (
                                 <li className={thumb.main === this.state.selectedShirt.main ? 'active': ''} onClick={this.selectShirt.bind(this, thumb)} key={thumb.id}>
                                     {/*<Image publicId={thumb.main}>*/}
                                         {/*<Transformation width="75" crop="scale" />*/}
                                     {/*</Image>*/}
                                     <ImageTransformations
                                         width="75"
                                         rgb={rgb}
                                         selectedShirt={thumb}
                                         text={' '} />
                                 </li>
                                 )
                              })}
                          </ul>
                      </div>
                  </div>
                  <div id="demoInputContainer">
                      <div className="inputSelections">
                          <h2>Shirt Color:</h2>
                          <SketchPicker
                              color={ this.state.background.hex }
                              onChangeComplete={ this.handleColorChange.bind(this) }
                          />
                      </div>
                  </div>
              </CloudinaryContext>
          </div>
        );
    }
}

export default App;

You’ve now extended the app, as follows:

  • react-color library is a color library with many options. With the option SketchPicker, you select a color and set the background state with that color by means of handleColorChange.
  • You set the rg, and b values of the image with the Transformation component.
  • The RGB values are negative because Cloudinary’s redblue, and green parameters adjust the respective color channel by percentage, and the color white in RGB is the maximum value of 256,256,256. The only way to go is down from white, hence the negative adjustments.
  • To display the two images, one of the model wearing a shirt and the other of a hanger, you apply the underlay and overlay transformation, respectively.

Note: The code manually updates the view with component.forceUpdate() because component.setState() is asynchronous, which causes delays in reflecting the changes.

Solving Problem 3: Custom Text

You can add text to images with the Cloudinary overlay feature, which is a standard service offered by companies that print custom text on fabrics. The code can’t be simpler:

<Transformation overlay="text:Roboto_30Scotch.io" />

Next, add the text Scotch.io in 30-px. Roboto font. Alternatively, add a text field to collect the text and update the image with the text on receipt of a keystroke, as follows:

import React, { Component } from 'react';
import {Image, CloudinaryContext, Transformation} from 'cloudinary-react';
import { SketchPicker } from 'react-color';
import './App.css';

const ImageTransformations = ({width, rgb, selectedShirt, text}) => {
    return (
        <Image publicId={selectedShirt.main+'.jpg'}>
            <Transformation overlay={'text:Roboto_30:'+text} flags="relative" gravity="center" />
        </Image>
    );
};

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            text: ' ',
          ...
        };
    }

   ...

    handleTextChange(event) {
        this.setState({text: event.target.value}, _ => this.forceUpdate())
    }

    render() {
        const rgb = this.state.background.rgb;

        return (
          <div className="App">
              <CloudinaryContext cloudName="christekh">
                  <div id="imageDemoContainer">
                  ..
                  </div>
                  <div id="demoInputContainer">
                      ...
                      <div className="inputSelections">
                          <h2>Text:</h2>
                          <input className="form-control" type="email" placeholder="Enter text" value={this.state.text} onChange={this.handleTextChange.bind(this)} />
                      </div>
                  </div>
              </CloudinaryContext>
          </div>
        );
    }
}

export default App;

We’ve truncated the above code sample so you can see the entire process.

Trying It Out

Using Cloudinary on your e-commerce site greatly eases the life of everyone on the creative team, opening up new opportunities that wouldn’t be available otherwise. Get started by signing up for free on the Cloudinary website.

The post Creating Interactive Product Pages With React and Cloudinary appeared first on Codrops.

10 Skills Product Managers Should Have in 2021

In 2020, managers had to supplement their skill sets to meet the needs of changing workplaces. People had little choice but to adjust to a remote set-up. The working world as we know it shifted. Both employees and managers had to change the way they operated. This prompted a rise in project planning software and video calling. 

Product managers have always required communication, organization, and technical skills. Now more so than ever, their vast skill sets are in demand to keep businesses on their feet. This article aims to highlight these essential skills. We will look at the part they’ll play going forward in the aftermath of 2020.

How We Used Arctype to Improve User Onboarding

Guiding Users to an 'A-ha' Moment

In 2020, ex-FB exec Chamath Palihapitiya shared his secret for growing Facebook to its first billion users. He had a laser focus on only 3 goals: getting users in the door, getting users to experience core value as fast as possible, and delivering core value as often as possible.

We wanted to see if we could apply these principles at Arctype and recreate the magic that Chamath engineered at Facebook. So last month we focused on the second goal, getting users to experience core value, and we saw some impressive results.

Kick-off Your Transformation By Imagining It Had Failed

Key Takeaways 

  • Large scale change initiatives (Lean, Agile, etc.) have a worryingly high failure rate. A chief reason for which is that serious risks are not identified early
  • People knowledgeable about the change initiative and its weaknesses are often reluctant to speak up about any serious misgivings they have, for fear of ruffling feathers or alienating colleagues
  • One way to create the requisite safety for everyone to speak openly about the risks they see – however unpalatable or impolitic they may be – is by running a pre-mortem workshop. Pre-mortems leverage a psychological technique called ‘prospective hindsight’ – imagining that the transformation had already failed, and walking backwards from there to investigate what led to the failure 
  • This technique had already been proven to increases the ability to correctly identify reasons for future outcomes by 30%
  • Facilitate a pre-mortem early in your transformation to maximize the value you gain from the activity and the insights it generates

What Is a Pre-Mortem?

When asked by the editor of the online science and technology magazine Edge.org to share one brilliant but overlooked concept or idea that he believes everyone should know, the Nobel laureate Richard H. Thaler, father of behavioural economics, former president of the American Economic Association and an intellectual giant of our time, did not hesitate. “The Pre-mortem!”, he said.

The idea of the pre-mortem is deceptively simple: before a major decision is made or a large project or initiative is undertaken, those involved in the effort get together to engage in what might seem like an oddly counterintuitive activity: imagining that they’re at some time in the future where the decision/project/initiative has been implemented and the results were a catastrophic failure, then writing a brief history of that failure - we failed, what could possibly be the reason? 

Agile Planning: Always Plan for a Product, Not a Project!

Planning in Agile involves so much more than a simple checklist.

Today in organizations, we plan and estimate to make effective decisions that help us stay competitive. We engage in this activity to get answers for simple questions like:

  • What needs to be built?
  • When can we complete?
  • How much will this cost?
  • Who needs to do it?

In traditional software delivery, we have been planning and estimating by following a sequential plan-driven approach. As these projects are short-lived once completed, there is no long-term connection between the product and the teams who built it. Also, more of the unfinished work and technical debt is left behind.

How to Get Your Engineering Team Involved in Product Efforts

At Anaxi, we don’t have a product manager. Seem weird? We’re not the only ones to do that. For instance, that’s also the case at Apple. But in our case, this role is spread out across our remote engineering team. In this article, I will detail why we do this and how we do it. To be honest, there are many things we’re still iterating on, and refining our processes will just be a constant effort, as it should be in every company. Note that our process works for any engineering team, working together in the same office or remotely.

Why You Want Your Dev Team Involved in Your Product Definition Efforts

In our particular case, our target customers are developers and engineering managers (and later on, product managers). So our engineering team also happens to represent our own customers. It stands to reason that they should have deep insights on what has value and what has less, probably more than a non-technical product manager who thinks they can understand the audience.