x

Using canvas to fix SVG scaling in Internet Explorer

Internet Explorer 9–11 suffer from various bugs that prevent proper scaling of inline SVG’s. This is particularly problematic for SVG icons with variable widths. This is the canvas-based hack I’ve been using to work around the issue.

A popular way to use SVG icons is to generate a spritemap of SVG symbol‘s that you then reference from elsewhere in a document. Most articles on the topic assume your icon dimensions are uniformly square. Twitter’s SVG icons (crafted by @sofo) are variable width, to produce consistent horizontal whitespace around the vectors.

Most browsers will preserve the intrinsic aspect ratio of an SVG. Ideally, I want to set a common height for all the icons (e.g., 1em), and let the browser scale the width of each icon proportionally. This also makes it easy to resize icons in particular contexts – just change the height.

Unfortunately, IE 9–11 do not preserve the intrinsic aspect ratio of an inline SVG. The svg element will default to a width of 300px (the default for replaced content elements). This means it’s not easy to work with variable-width SVG icons. No amount of CSS hacking fixed the problem, so I looked elsewhere – and ended up using canvas.

canvas and aspect ratios

A canvas element – with height and width attributes set – will preserve its aspect ratio when one dimension is scaled. The example below sets a 3:1 aspect ratio.

<canvas height="1" width="3"></canvas>

You can then scale the canvas by changing either dimension in CSS.

canvas {
  display: block;
  height: 2rem;
}

Demo: proportional scaling of canvas.

Fixing SVG scaling in IE

This makes canvas useful for creating aspect ratios. Since IE doesn’t preserve the intrinsic aspect ratio of SVG icons, you can use canvas as a shim. A canvas of the correct aspect ratio provides a scalable frame. The svg can then be positioned to fill the space created by this frame.

The HTML is straightforward:

<div class="Icon" role="img" aria-label="Twitter">
  <canvas class="Icon-canvas" height="1" width="3"></canvas>
  <svg class="Icon-svg">
    <use fill="currentcolor" xlink:href="#icon-twitter"></use>
  </svg>
</div>

So is the CSS:

.Icon {
  display: inline-block;
  height: 1em; /* default icon height */
  position: relative;
  user-select: none;
}

.Icon-canvas {
  display: block;
  height: 100%;
  visibility: hidden;
}

.Icon-svg {
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

Setting the canvas height to 100% means it will scale based on the height of the component’s root element – just as SVG’s do in non-IE browsers. Changing the height of the Icon element scales the inner SVG icon while preserving its 3:1 aspect ratio.

Demo: proportional scaling of svg in IE.

Creating an Icon component

The hack is best added to (and eventually removed from) an existing icon component’s implementation.

If you’re generating and inlining an SVG spritemap, you will need to extract the height and width (usually from viewBox) of each of your icons during the build step. If you’re already using the gulp-svgstore plugin, it supports extracting metadata.

Those dimensions need to be set on the canvas element to produce the correct aspect ratio for a given icon.

Example React component (built with webpack):

import iconData from './lib/icons-data.json';
import React from 'react';
import './index.css';

class Icon extends React.Component {
  render() {
    const props = this.props;
    const height = iconData[props.name.height];
    const width = iconData[props.name.width];

    // React doesn't support namespaced attributes, so we have to set the
    // 'use' tag with innerHTML
    const useTag = `<use fill="currentcolor"
                      xlink:href="#icon-${props.name}">
                    </use>`;

    return (
      <span className="Icon">
        <canvas className="Icon-canvas"
          height={height}
          width={width}
        />
        <svg className="Icon-svg"
          dangerouslySetInnerHTML={{__html: useTag}}
          key={props.name}
        />
      </span>
    );
  }
}

export default Icon;

When I introduced this hack to a code base at Twitter, it had no impact on the the rest of the team or the rest of the code base – one of the many benefits of a component-based UI.




x

Redux modules and code-splitting

Twitter Lite uses Redux for state management and relies on code-splitting. However, Redux’s default API is not designed for applications that are incrementally-loaded during a user session.

This post describes how I added support for incrementally loading the Redux modules in Twitter Lite. It’s relatively straight-forward and proven in production over several years.

Redux modules

Redux modules comprise of a reducer, actions, action creators, and selectors. Organizing redux code into self-contained modules makes it possible to create APIs that don’t involve directly referencing the internal state of a reducer – this makes refactoring and testing a lot easier. (More about the concept of redux modules.)

Here’s an example of a small “redux module”.

// data/notifications/index.js

const initialState = [];
let notificationId = 0;

const createActionName = name => `app/notifications/${name}`;

// reducer
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case ADD_NOTIFICATION:
      return [...state, { ...action.payload, id: notificationId += 1 }];
    case REMOVE_NOTIFICATION:
      return state.slice(1);
    default:
      return state;
  }
}

// selectors
export const selectAllNotifications = state => state.notifications;
export const selectNextNotification = state => state.notifications[0];

// actions
export const ADD_NOTIFICATION = createActionName(ADD_NOTIFICATION);
export const REMOVE_NOTIFICATION = createActionName(REMOVE_NOTIFICATION);

// action creators
export const addNotification = payload => ({ payload, type: ADD_NOTIFICATION });
export const removeNotification = () => ({ type: REMOVE_NOTIFICATION });

This module can be used to add and select notifications. Here’s an example of how it can be used to provide props to a React component.

// components/NotificationView/connect.js

import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { removeNotification, selectNextNotification } from '../../data/notifications';

const mapStateToProps = createStructuredSelector({
  nextNotification: selectNextNotification
});
const mapDispatchToProps = { removeNotification };

export default connect(mapStateToProps, mapDispatchToProps);
// components/NotificationView/index.js

import connect from './connect';
export class NotificationView extends React.Component { /*...*/ }
export default connect(NotificationView);

This allows you to import specific modules that are responsible for modifying and querying specific parts of the overall state. This can be very useful when relying on code-splitting.

However, problems with this approach are evident once it comes to adding the reducer to a Redux store.

// data/createStore.js

import { combineReducers, createStore } from 'redux';
Import notifications from './notifications';

const initialState = /* from local storage or server */

const reducer = combineReducers({ notifications });
const store = createStore(reducer, initialState);

export default store;

You’ll notice that the notifications namespace is defined at the time the store is created, and not by the Redux module that defines the reducer. If the “notifications” reducer name is changed in createStore, all the selectors in the “notifications” Redux module no longer work. Worse, every Redux module needs to be imported in the createStore file before it can be added to the store’s reducer. This doesn’t scale and isn’t good for large apps that rely on code-splitting to incrementally load modules. A large app could have dozens of Redux modules, many of which are only used by a few components and unnecessary for initial render.

Both of these issues can be avoided by introducing a Redux reducer registry.

Redux reducer registry

The reducer registry enables Redux reducers to be added to the store’s reducer after the store has been created. This allows Redux modules to be loaded on-demand, without requiring all Redux modules to be bundled in the main chunk for the store to correctly initialize.

// data/reducerRegistry.js

export class ReducerRegistry {
  constructor() {
    this._emitChange = null;
    this._reducers = {};
  }

  getReducers() {
    return { ...this._reducers };
  }

  register(name, reducer) {
    this._reducers = { ...this._reducers, [name]: reducer };
    if (this._emitChange) {
      this._emitChange(this.getReducers());
    }
  }

  setChangeListener(listener) {
    this._emitChange = listener;
  }
}

const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;

Each Redux module can now register itself and define its own reducer name.

// data/notifications/index.js

import reducerRegistry from '../reducerRegistry';

const initialState = [];
let notificationId = 0;

const reducerName = 'notifications';

const createActionName = name => `app/${reducerName}/${name}`;

// reducer
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case ADD_NOTIFICATION:
      return [...state, { ...action.payload, id: notificationId += 1 }];
    case REMOVE_NOTIFICATION:
      return state.slice(1);
    default:
      return state;
  }
}

reducerRegistry.register(reducerName, reducer);

// selectors
export const selectAllNotifications = state => state[reducerName];
export const selectNextNotification = state => state[reducerName][0];

// actions
export const ADD_NOTIFICATION = createActionName(ADD_NOTIFICATION);
export const REMOVE_NOTIFICATION = createActionName(REMOVE_NOTIFICATION);

// action creators
export const addNotification = payload => ({ payload, type: ADD_NOTIFICATION });
export const removeNotification = () => ({ type: REMOVE_NOTIFICATION });

Next, we need to replace the store’s combined reducer whenever a new reducer is registered (e.g., after loading an on-demand chunk). This is complicated slightly by the need to preserve initial state that may have been created by reducers that aren’t yet loaded on the client. By default, once an action is dispatched, Redux will throw away state that is not tied to a known reducer. To avoid that, reducer stubs are created to preserve the state.

// data/createStore.js

import { combineReducers, createStore } from 'redux';
import reducerRegistry from './reducerRegistry';

const initialState = /* from local storage or server */

// Preserve initial state for not-yet-loaded reducers
const combine = (reducers) => {
  const reducerNames = Object.keys(reducers);
  Object.keys(initialState).forEach(item => {
    if (reducerNames.indexOf(item) === -1) {
      reducers[item] = (state = null) => state;
    }
  });
  return combineReducers(reducers);
};

const reducer = combine(reducerRegistry.getReducers());
const store = createStore(reducer, initialState);

// Replace the store's reducer whenever a new reducer is registered.
reducerRegistry.setChangeListener(reducers => {
  store.replaceReducer(combine(reducers));
});

export default store;

Managing the Redux store’s reducer with a registry should help you better code-split your application and modularize your state management.




x

Odisha to expedite chariot construction for Rath Yatra

The Home Ministry had on Thursday allowed chariot construction with a condition that no religious congregation should take place around the Ratha Khala.




x

Former J&K Minister’s detention extended by 3 months

With a view to prevent him from acting in any manner prejudicial to the maintenance of public order, Naeem Akhtar detention is being extended, an order said.




x

International experts to be consulted on Styrene gas leak at Visakhapatnam

The NCMC chaired by Cabinet Secretary Rajiv Gauba met on Friday to review the situation arising out of the gas leak




x

Maximum alert against killer COVID-19




x

Strategic excellence in the architecture, engineering, and construction industries [electronic resource] : how AEC firms can develop and execute strategy using lean Six Sigma / Gerhard Plenert and Joshua J. Plenert

Plenert, Gerhard Johannes, author




x

Strategische personalentwicklung in der praxis [electronic resource] : instrumente, erfolgsmodelle, checklisten, praxisbeispiele. / Christine Wegerich

Wegerich, Christine, author




x

The strategy of execution [electronic resource] : the five-step guide for turning vision into action / Liz Mellon and Simon Carter

Mellon, Elizabeth




x

Streamlining business requirements [electronic resource] : the XCellR8 approach / Gerrie Caudle

Caudle, Gerrie




x

Supply chain design (collection) [electronic resource] / Marc J. Schniederjans [and six others]

Schniederjans, Marc J., author




x

Tapping into unstructured data [electronic resource] : integrating unstructured data and textual analytics into business intelligence / William H. Inmon, Anthony Nesavich

Inmon, William H




x

Testdaten und Testdatenmanagement [electronic resource] : Vorgehen, Methoden und Praxis / Janet Albrecht-Zölch

Albrecht-Zölch, Janet, author




x

Thinkers 50 innovation [electronic resource] : breakthrough thinking to take your business to the next level / Stuart Crainer + Des Dearlove

Crainer, Stuart




x

Thinkers 50 strategy [electronic resource] : the art and science of strategy creation and execution / by Stuart Crainer + Des Dearlove

Crainer, Stuart




x

Thriving under stress [electronic resource] : harnessing demands in the workplace / Thomas W. Britt, Ph.D., Professor of Psychology, Clemson University, Steve M. Jex, Ph.D., Professor of Psychology, Bowling Green State University

Britt, Thomas W., 1966-




x

Toyota China [electronic resource] : matching supply with demand / Chuck Munson with Xiaoying Liang, Lijun Ma, and Houmin Yan

Munson, Chuck, author




x

The truth about making complex or critical decisions [electronic resource] : the essential truths in 20 minutes / Robert E. Gunther

Gunther, Robert E., 1960- author




x

Unternehmensführung [electronic resource] : Strategie, Management, Praxis / Hans-Erich Müller

Müller, Hans-Erich, 1945- author




x

Using 360-degree feedback successfully [electronic resource] / Maxine A. Dalton

Dalton, Maxine A., author




x

Using IBM DB2 UDB with IBM System Storage N series [electronic resource] / [Alex Osuna ... et al.]




x

UX Fundamentals for Non-UX Professionals [electronic resource] : User Experience Principles for Managers, Writers, Designers, and Developers / by Edward Stull

Stull, Edward. author




x

Welcome to management [electronic resource] : how to go from top performer to excellent leader / by Ryan Hawk

Hawk, Ryan, author




x

What every leader should know about expatriate effectiveness [electronic resource] / Meena S. Wilson, Maxine A. Dalton

Wilson, Meena S., author




x

What is six sigma? [electronic resource] / Pete Pande, Larry Holpp

Pande, Peter S




x

Why American elections are flawed (and how to fix them) [electronic resource] / Pippa Norris

Norris, Pippa, author




x

XML and JSON Recipes for SQL Server [electronic resource] : A Problem-Solution Approach / by Alex Grinberg

Grinberg, Alex. author




x

Your first 100 days [electronic resource] : how to make maximum impact in your new leadership role / Niamh O'Keeffe

O'Keeffe, Niamh




x

Zero Trust Networks with VMware NSX [electronic resource] : Build Highly Secure Network Architectures for Your Data Centers / by Sreejith Keeriyattil

Keeriyattil, Sreejith. author




x

JAMA Otolaryngology–Head & Neck Surgery : Effect of a Change in Papillary Thyroid Cancer Terminology on Anxiety Levels and Treatment Preferences

Interview with Brooke Nickel and Juan Brito, MD, MSc, authors of Effect of a Change in Papillary Thyroid Cancer Terminology on Anxiety Levels and Treatment Preferences: A Randomized Crossover Trial




x

JAMA Neurology : Effect of Dextroamphetamine on Poststroke Motor Recovery

Interview with Larry B. Goldstein, MD, author of Effect of Dextroamphetamine on Poststroke Motor Recovery: A Randomized Clinical Trial








x

JAMA Internal Medicine : Effect of Exercise Intervention on Functional Decline in Very Elderly Patients

Interview with Mikel Izquierdo, Ph.D, and Nicolás Martínez-Velilla, PhD, authors of Effect of Exercise Intervention on Functional Decline in Very Elderly Patients During Acute Hospitalization: A Randomized Clinical Trial, and William J. Hall, MD, author of A Novel Exercise Intervention and Functional Status in Very Elderly Patients During Acute Hospitalization











x

JAMA Cardiology : Rivaroxaban and Thromboembolic Events in Patients With Heart Failure, Coronary Disease, and Sinus Rhythm

Interview with Barry H. Greenberg, author of Association of Rivaroxaban With Thromboembolic Events in Patients With Heart Failure, Coronary Disease, and Sinus Rhythm: A Post Hoc Analysis of the COMMANDER HF Trial, and Marvin A. Konstam, MD, author of Antithrombotic Therapy in Heart Failure—The Clot Thickens