How We Built JD Retail Design Portal: Architecture, Animations, and Performance Hacks

This article details the end‑to‑end development of the JDR DESIGN portal, covering the chosen front‑end stack, project architecture, development workflow, performance optimizations such as lazy loading and webpack splitting, and the implementation of complex Lottie‑based animations.

Aotu Lab
Aotu Lab
Aotu Lab
How We Built JD Retail Design Portal: Architecture, Animations, and Performance Hacks

Project Overview

JDR DESIGN is the portal site for JD Retail Design, showcasing products and use cases with rich motion effects, high‑resolution images, and configurable copy and external links. The project faced tight schedules and complex animation requirements.

Technical Stack

The team selected the self‑developed Nerv framework, a lightweight Virtual DOM‑based React‑like library that supports IE10, and the internal build tool Athena to simplify Webpack configuration, automate compilation, dependency analysis, and asset hashing.

Overall Architecture

Key components include:

Common utility library with functions such as Nerv-loadable for dynamic imports.

NEOS management platform for centralized copy and link data.

Fallback displays for error pages and broken images.

MTA data analysis for click‑event tracking and real‑time monitoring.

Development Process

During the first month the team tackled missing design specs, floor‑level lazy loading, IE compatibility, skeleton screens, and data tracking. Missing steps in the initial workflow caused delays, prompting the addition of a comprehensive design‑spec checklist, floor structure planning, and explicit IE‑unsafe API avoidance.

Performance Optimizations

To accelerate load times, the team applied several techniques:

Floor Lazy Loading

Components are split per floor and loaded on demand using Nerv-loadable and the native import() syntax.

const NewsBannerLoadable = Loadable({
  loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'),
  loading: loadingPlaceholder.bind(null, loadingBlock),
  delay: 0
});

The initial render shows a loadingBlock placeholder, then dynamically loads the actual component.

Image Lazy Loading

Images are wrapped with <Lazyload> to load only when they enter the viewport. Lazyimg handles loading states (loading, success, failure) by creating a new Image() object and listening to onload / onerror.

<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}>
  <NewsBannerLoadable />
</Lazyload>

Webpack Splitting

Using Webpack’s SplitChunks plugin, common dependencies are extracted into separate chunks, reducing bundle size.

Skeleton Screens

Skeleton placeholders replace loading animations to minimize visual gaps. The site uses custom CSS‑based skeletons rather than a global component library.

Responsive Layout

The design supports both wide (1240 px) and narrow views. A wide/narrow class is added to the html element at runtime to switch styles.

!function(e){
  window.pageConfig={};
  pageConfig.isWide=function(){
    var n=e,i=document,o=document.documentElement,t=document.getElementsByTagName("body")[0],a=n.innerWidth||o.clientWidth||t.clientWidth;
    return a>=1300;
  }();
  var n=[];pageConfig.isWide?n.push("wide"):n.push("narrow");
  var i=document.getElementsByTagName("html")[0];
  i.className=n.join(" ");
}(window,void 0);

Animation Development

Home Icon Animation with Lottie

The team replaced hand‑crafted CSS keyframes with Lottie, loading After Effects JSON exported via the bodymovin plugin. The animation is initialized with lottie-web and controlled via playSegments, setDirection, and play / pause methods.

npm install lottie-web // install
import lottie from 'lottie-web';
this.anim = lottie.loadAnimation({
  container: element,
  renderer: 'svg',
  loop: true,
  autoplay: true,
  path: 'data.json'
});
this.anim.playSegments([[0,60]], true);
this.anim.setDirection(-1);
this.anim.play();
this.anim.pause();

Important notes: JSON files must be served via CDN; if the animation references external images, the export format becomes json+img, which may affect compatibility.

Entrance Animation

To avoid conflict with skeleton screens, a two‑layer lazy‑loading strategy is used: floor components load at an offset of 200 px, while entrance‑animation components load at an offset of –200 px, ensuring the animation runs only after the real component is visible.

// Load floor component when 200px from bottom
getMaterialLoadable(){
  return this.getFloor(
    <Lazyload lazyloadOptions={{offset:200}} height={1000}>
      <MaterialLoadable />
    </Lazyload>
  );
}
// Load entrance animation when -200px from bottom
<Lazyload lazyloadOptions={{offset:-200}}>
  <div className="w">
    <IndexTitle showLine={true} title={this.state.title} />
    {this.renderMaterial()}
  </div>
</Lazyload>

Product Page Header Animation

Two parts: ambient animation (Lottie) and wave animation (sine_wave library). Custom SVG gradients are applied by targeting generated IDs, which may change between builds, so both possible IDs must be styled.

new SineWaves({
  el: document.getElementById(`waves`),
  speed: 0.75,
  width: function(){return document.body.clientWidth;},
  height: 68,
  ease: 'Linear',
  waves: [{
    "timeModifier":1,
    "lineWidth":1,
    "amplitude":30*window.devicePixelRatio,
    "wavelength":125*window.devicePixelRatio,
    "strokeStyle":"rgba(221,221,233,1)",
    "type":function(x,waves){return waves.sine(x+8);}
  }],
  rotate:0,
  wavesWidth:'400%'
});

Amplitude and wavelength are scaled by window.devicePixelRatio to handle high‑DPI screens.

Global Detail Animations

Common animation variables are managed with a SASS variable $common_animation for consistency.

Icon and border animations use SVG to keep vector quality.

Visibility toggling is done via the visibility property instead of display for smoother transitions.

Overall Summary

The case study walks through project architecture, development workflow, and optimization techniques for the JDR DESIGN site, highlighting challenges faced, solutions implemented, and lessons learned for future front‑end projects.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationLottieReactWebpacklazy-loading
Aotu Lab
Written by

Aotu Lab

Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.