Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically change color of SVG with JS #43

Closed
plmok61 opened this issue Mar 1, 2019 · 6 comments
Closed

Dynamically change color of SVG with JS #43

plmok61 opened this issue Mar 1, 2019 · 6 comments

Comments

@plmok61
Copy link

plmok61 commented Mar 1, 2019

I have build an Icon component that allows me to pass a fill color as props to an svg. It works if the fill color remains static, but I would like to have the colors update if new fill props are passed. In the code bellow, I am re-injecting the svg with the new color props in componentDidUpdate but the svg keeps the old colors in the browser. Could this be caused by cacheing? If so, how to I get around this?

import React, { PureComponent, createRef } from 'react';
import PropTypes from 'prop-types';
import SVGInjector from '@tanem/svg-injector';

class Icon extends PureComponent {
  static propTypes = {
    /**
     * Name of the SVG icon
     */
    name: PropTypes.string.isRequired,
    /**
     * Optional fill color - default is black
     * If an array is passed the colors will be mapped over the <path> tags within the SVG
     */
    fill: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]),
    /**
     * Optional CSS class
     */
    className: PropTypes.string,
    /**
     * Optional style object
     */
    style: PropTypes.shape({}),
  };

  static defaultProps = {
    fill: '#000',
    className: '',
    style: {},
  };

  constructor(props) {
    super(props);
    this.svgRef = createRef();
    this.createSvgOptions = this.createSvgOptions.bind(this);
  }

  componentDidMount() {
    SVGInjector(this.svgRef.current, this.createSvgOptions());
  }

  componentDidUpdate(prevProps) {
    if (prevProps.fill !== this.props.fill) {
      SVGInjector(this.svgRef.current, this.createSvgOptions());
    }
  }

  createSvgOptions() {
    const { fill } = this.props;
    return {
      each(err, svg) {
        if (err) {
          throw err;
        }
        const paths = Array.from(svg.getElementsByTagName('path'));
        if (typeof fill === 'string') {
          paths.forEach(path => path.setAttribute('fill', fill));
        } else if (Array.isArray(fill)) {
          paths.forEach((path, i) => {
            const color = fill[i] ? fill[i] : fill[0]; // if less colors than paths, use first color
            path.setAttribute('fill', color);
          });
        }
      },
    }
  }

  render() {
    const { className, style, name } = this.props;
    return (
      <span
        ref={this.svgRef}
        className={className}
        style={style}
        data-src={`https://my-cdn.com/icons/${name}.svg`}
      />
    );
  }
}

export default Icon;
@plmok61 plmok61 changed the title Change color of SVG with JS Dynamically change color of SVG with JS Mar 1, 2019
@tanem
Copy link
Owner

tanem commented Mar 2, 2019

Hey @plmok61. At first glance I don't think caching should get in the way here, as the each callback should still be called each time.

Do you happen to have a reference to one of the SVGs you are using in this case, so I can have a go at an accurate repro? (e.g. a name I can plug into that data-src URL: https://my-cdn.com/icons/${name}.svg).

@tanem
Copy link
Owner

tanem commented Mar 2, 2019

Ignore my previous comment, I'm just playing around with my own dummy SVG for now. Still haven't had the chance to investigate properly so will get back to you once I have an answer.

PS, react-svg is a thing, it's actually based on this library so if you're doing things in a React context I'd recommend taking a look at that too, since it deals with a lot of the edge cases.

@tanem
Copy link
Owner

tanem commented Mar 2, 2019

... so if you use react-svg, your component could end up looking something like this, which removes the need to track refs etc:

import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import ReactSVG from 'react-svg'

class Icon extends PureComponent {
  static propTypes = {
    /**
     * Name of the SVG icon
     */
    name: PropTypes.string.isRequired,
    /**
     * Optional fill color - default is black
     * If an array is passed the colors will be mapped over the <path> tags within the SVG
     */
    fill: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string)
    ]),
    /**
     * Optional CSS class
     */
    className: PropTypes.string,
    /**
     * Optional style object
     */
    style: PropTypes.shape({})
  }

  static defaultProps = {
    fill: '#000',
    className: '',
    style: {}
  }

  onInjected = (err, svg) => {
    const { fill } = this.props
    const paths = Array.from(svg.getElementsByTagName('path'))
    if (typeof fill === 'string') {
      paths.forEach(path => path.setAttribute('fill', fill))
    } else if (Array.isArray(fill)) {
      paths.forEach((path, i) => {
        const color = fill[i] ? fill[i] : fill[0] // if less colors than paths, use first color
        path.setAttribute('fill', color)
      })
    }
  }

  render() {
    const { className, style, name } = this.props

    // Passing a `key` here so `ReactSVG` re-renders when fill changes.
    return (
      <ReactSVG
        key={this.props.fill}
        onInjected={this.onInjected}
        svgClassName={className}
        svgStyle={style}
        src={`https://my-cdn.com/icons/${name}.svg`}
      />
    )
  }
}

export default Icon

Just heading out the door so will post back later with a working example 👍

@tanem
Copy link
Owner

tanem commented Mar 3, 2019

Here's the working example.

In short, since you're using React, my recommendation is to use react-svg for this use-case. It solves a bunch of issues you'll likely encounter when trying to co-ordinate the React lifecycle with svg-injector, which meddles with the DOM directly.

I'll keep the issue open for now though, feel free to fire any more questions my way.

@plmok61
Copy link
Author

plmok61 commented Mar 4, 2019

I was able to get it working using react-svg. Thank you for looking into this!

@plmok61 plmok61 closed this as completed Mar 4, 2019
@tanem
Copy link
Owner

tanem commented Mar 4, 2019

Great news, no worries @plmok61 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants