Giter Site home page Giter Site logo

tanem / svg-injector Goto Github PK

View Code? Open in Web Editor NEW
96.0 2.0 14.0 9.7 MB

:syringe: Fast, caching, dynamic inline SVG DOM injection library.

Home Page: https://npm.im/@tanem/svg-injector

License: MIT License

JavaScript 7.16% TypeScript 92.84%
dom html images img javascript scalable-vector-graphics svg typescript

svg-injector's Introduction

svg-injector

npm version build status coverage status npm downloads minzipped size

A fast, caching, dynamic inline SVG DOM injection library.

Background

There are a number of ways to use SVG on a page (object, embed, iframe, img, CSS background-image) but to unlock the full potential of SVG, including full element-level CSS styling and evaluation of embedded JavaScript, the full SVG markup must be included directly in the DOM.

Wrangling and maintaining a bunch of inline SVG on your pages isn't anyone's idea of good time, so SVGInjector lets you work with simple tag elements and does the heavy lifting of swapping in the SVG markup inline for you.

Basic Usage

<div id="inject-me" data-src="icon.svg"></div>
import { SVGInjector } from '@tanem/svg-injector'

SVGInjector(document.getElementById('inject-me'))

Avoiding XSS

Be careful when injecting arbitrary third-party SVGs into the DOM, as this opens the door to XSS attacks. If you must inject third-party SVGs, it is highly recommended to sanitize the SVG before injecting. The following example uses DOMPurify to strip out attributes and tags that can execute arbitrary JavaScript. Note that this can alter the behavior of the SVG.

import { SVGInjector } from '@tanem/svg-injector'
import DOMPurify from 'dompurify'

SVGInjector(document.getElementById('inject-me'), {
  beforeEach(svg) {
    DOMPurify.sanitize(svg, {
      IN_PLACE: true,
      USE_PROFILES: { svg: true, svgFilters: true },
    })
  },
})

Live Examples

API

Arguments

  • elements - A single DOM element or array of elements, with src or data-src attributes defined, to inject.
  • options - Optional An object containing the optional arguments defined below. Defaults to {}.
    • afterAll(elementsLoaded) - Optional A callback which is called when all elements have been processed. elementsLoaded is the total number of elements loaded. Defaults to () => undefined.
    • afterEach(err, svg) - Optional A callback which is called when each element is processed. svg is the newly injected SVG DOM element. Defaults to () => undefined.
    • beforeEach(svg) - Optional A callback which is called just before each SVG element is added to the DOM. svg is the SVG DOM element which is about to be injected. Defaults to () => undefined.
    • cacheRequests - Optional Use request cache. Defaults to true.
    • evalScripts - Optional Run any script blocks found in the SVG. One of 'always', 'once', or 'never'. Defaults to 'never'.
    • httpRequestWithCredentials - Optional Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials. Defaults to false.
    • renumerateIRIElements - Optional Boolean indicating if SVG IRI addressable elements should be renumerated. Defaults to true.

Example

<div class="inject-me" data-src="icon-one.svg"></div>
<div class="inject-me" data-src="icon-two.svg"></div>
import { SVGInjector } from '@tanem/svg-injector'

SVGInjector(document.getElementsByClassName('inject-me'), {
  afterAll(elementsLoaded) {
    console.log(`injected ${elementsLoaded} elements`)
  },
  afterEach(err, svg) {
    if (err) {
      throw err
    }
    console.log(`injected ${svg.outerHTML}`)
  },
  beforeEach(svg) {
    svg.setAttribute('stroke', 'red')
  },
  cacheRequests: false,
  evalScripts: 'once',
  httpRequestWithCredentials: false,
  renumerateIRIElements: false,
})

Installation

โš ๏ธThis library uses Array.from(), so if you're targeting browsers that don't support that method, you'll need to ensure an appropriate polyfill is included manually. See this issue comment for further detail.

$ npm install @tanem/svg-injector

There are also UMD builds available via unpkg:

Credit

This is a fork of a library originally developed by Waybury for use in iconic.js, part of the Iconic icon system.

License

MIT

svg-injector's People

Contributors

dependabot[bot] avatar derrickpelletier avatar isgabe avatar protodave avatar pwntastic avatar renovate-bot avatar renovate[bot] avatar steven-wolfman avatar stryju avatar tanem avatar timhwang21 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

svg-injector's Issues

Injecting from `<img>` should takes its attributes

For example, width, height, alt, title, etc...

This is probably opinionated. For me, if I use the SVG in <img>, and I set width and height, I would think that the injected SVG should keep the same dimension. Otherwise, when using in IE10-, if the SVG itself does not have width and height, IE will display it using the default dimension.

Dynamically change color of SVG with JS

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;

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • Lock file maintenance

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • Update dependency @types/webpack-env to v1.18.5
  • Update dependency tanem-scripts to v7.0.27
  • Update dependency typescript to v5.4.5
  • Update dependency rollup to v4.17.2
  • Update typescript-eslint monorepo to v7.8.0 (@typescript-eslint/eslint-plugin, @typescript-eslint/parser)
  • Update dependency eslint to v9
  • ๐Ÿ” Create all rate-limited PRs at once ๐Ÿ”

Pending Status Checks

These updates await pending status checks. To force their creation now, click the checkbox below.

  • Update codecov/codecov-action action to v4

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • styfle/cancel-workflow-action 0.12.1
  • actions/checkout v4
  • actions/checkout v4
  • actions/setup-node v4
  • codecov/codecov-action v3
npm
examples/api-usage/package.json
  • parcel 2.10.0
examples/basic-usage/package.json
  • parcel 2.10.0
examples/mootools/package.json
  • parcel 2.10.0
examples/umd-dev/package.json
examples/umd-prod/package.json
package.json
  • @babel/runtime ^7.23.2
  • content-type ^1.0.5
  • tslib ^2.6.2
  • @babel/core 7.23.2
  • @babel/plugin-transform-runtime 7.23.2
  • @babel/preset-env 7.23.2
  • @babel/preset-typescript 7.23.2
  • @rollup/plugin-babel 6.0.4
  • @rollup/plugin-commonjs 25.0.7
  • @rollup/plugin-node-resolve 15.2.3
  • @rollup/plugin-replace 5.0.5
  • @rollup/plugin-terser 0.4.4
  • @types/chai 4.3.8
  • @types/content-type 1.1.8
  • @types/karma 6.3.8
  • @types/karma-chai 0.1.6
  • @types/mocha 9.1.1
  • @types/sinon 9.0.11
  • @types/ua-parser-js 0.7.39
  • @types/webpack-env 1.18.4
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • babel-loader 9.1.3
  • babel-plugin-istanbul 6.1.1
  • chai 4.3.10
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • karma 6.4.3
  • karma-browserstack-launcher 1.6.0
  • karma-chai 0.1.0
  • karma-chrome-launcher 3.2.0
  • karma-coverage-istanbul-reporter 3.0.3
  • karma-mocha 2.0.1
  • karma-sourcemap-loader 0.4.0
  • karma-spec-reporter 0.0.36
  • karma-webpack 5.0.1
  • mocha 9.2.2
  • npm-run-all 4.1.5
  • null-loader 4.0.1
  • prettier 3.0.3
  • query-string 7.1.3
  • rollup 4.13.1
  • rollup-plugin-filesize 10.0.0
  • shx 0.3.4
  • sinon 9.2.4
  • tanem-scripts 7.0.26
  • typescript 5.4.4
  • ua-parser-js 1.0.37
  • webpack 5.91.0
nvm
.nvmrc

  • Check this box to trigger a request for Renovate to run again on this repository

renumerateIRIElements for path defs

In some of the exported SVGs, inside defs I have something like:

<defs>
     <path id="a" d="...." />
</defs>

Which is then used inside <g>, as:

<g>
    <mask id="b" fill="#fff">
        <use xlink:href="#a" />
    </mask>
    <use fill="#000" xlink:href="#a"/>
    <g mask="url(#b)">
        <path fill="#FFF" d="...."/>
    </g>
</g>

When having many elements like these, the svg graphics get mixed up. This example also has the problem of defining the id of the mask outside <defs>, and using reference it by id... should I raise a separate issue for that?

Cookie: token value is not adding while image call

Hello tanem,

We are using image calls to have an image and I am using (load)="SVGInjector()". Although we are setting the token for the call in cookies and in the request header it should pass the token but it seems like it's not. Do you have a solution for it? (we tried by putting httpRequest.withCredentials = true; in file https://github.com/tanem/svg-injector/blob/master/src/make-ajax-request.ts) and it works so do we have another version where we can have something like this?

Sometimes svg is not injected

Sorry my code is behind a firewall so it is not available to run an example. I have a page where I use the same svg source in multiple places but sometimes the svg fails to be injected. Instead I get the following div.

<div data-src="Pub/Web/Icon/arrowDropDown.svg" src(unknown)></div>

If I perform an action to cause the svg container to render again the svg will show. I am running on a web server not locally. Any advice is appreciated.

Fallback at httpRequest.responseXML instanceof Document check

Hello tahem!

We've run into one very nasty conflict on several client sites where we use the SVG Injector.

They use a very very ancient library called "mootools" with this interesting method that overriding window.Document (such a shame). https://mootools.net/core/docs/1.6.0/Element/Element#Window:document-id

image

As a consequence, the instanceof Document check fails. (load-svg-cached.ts and load-svg-uncached.ts)

From the user-land side, we cannot influence this in any way and are thinking about a stub.

Could you add some fallback if this check fails on your end?

cannot install - not in the registry

npm ERR! 404 Not Found - GET https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-8.0.61.tgz
npm ERR! 404
npm ERR! 404 '@tanem/[email protected]' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
npm ERR! A complete log of this run can be found in:
npm ERR! /root/.npm/_logs/2020-08-06T08_58_33_447Z-debug.log

CORS issue when referencing SVGs from CDN.

It seems after some frustrating time that the plugin doesn't correctly handle SVGs that are hosted on and CDN like Cloudfront. Might this issue be related?

To me it seems such a big issue that I'm starting to doubt myself if I just am missing something obvious.

Cache only if successful response

Hi there,
I'm using this library through react-svg in a server-less PWA I maintain. As a PWA, it caches the assets, so it can work offline. But some data-related assets are not downloaded until requested by the user. Sometimes, the user may forget to disable phone's airplane mode before using the app. Note: In this app, the SVG related component can be seen by adding a Map Widget.

I am developing an error boundary which allows re-rendering the errorish component once the source of the error have been fixed (for example, disabling phone's airplane mode). In this case, I expect the ReactSVG component to retry the download. Instead of that, it reuses a cached errorish version of the fetched data. Note: Using DevTools Network, I am able to mock an initial error from server by blocking the SVG URL, and then "un-block" the net request for the SVG to retry the component render.

As a workaround, I create a global object where I store successful downloads by src (set on afterInjection) and set react.svg's useRequestCache (same as this package's cacheRequests) to false until a successful download is detected.

I think it would be much better to don't cache errorish responses (unless -maybe- otherwise stated).

Note: I open the issue directly here, as I think the fix would be desired here, too, and the author is the same for both projects.

IE11 support

Sorry to be the one that brings up IE support, but I've tracked down react-svg not working to here :(

https://github.com/tanem/svg-injector/blob/master/src/inject-element.ts#L85 uses Array.from which isn't in IE11 yet. I've got core-js in my webpack build but I'm using babel-preset-env with useBuiltins set to usage, but because your code comes from an npm module, webpack never checks it so I don't get the polyfill for Array.from.

Anything you can do to fix, or shall I just force the Array.from polyfill from core-js into my build?

Element is the wrong type for SVG?

I'm using react-svg and the beforeInjection callback doesn't let me access the style property on the svg. It's because the type is Element.

const beforeInject = (svg: Element) => {
  svg.style.backgroundImage = `url(${texture.url})` // TS2339: Property 'style' does not exist on type 'Element'.
  svg.style.width = `${size.width}px`,
  svg.style.clipPath = `url(#${id})`
}

It's not really a problem as this works

const beforeInject = (s: Element) => {
  const svg = s as HTMLElement

  svg.style.backgroundImage = `url(${texture.url})`
  svg.style.width = `${size.width}px`,
  svg.style.clipPath = `url(#${id})`
}

but I was wondering if it's intended or a mistake?

Wrong clipPath ids - uniqueId not set properly on clip-path style attribute

Hello, we use react-svg and have trouble with clip-paths not being applied correctly, turns out clip-path ids get randomized, but same ids are not updated in clip-path style attribute

elements = svg.querySelectorAll(element + '[id]')
for (let a = 0, elementsLen = elements.length; a < elementsLen; a++) {
currentId = elements[a].id
newId = currentId + '-' + uniqueId()
// All of the properties that can reference this element type.
let referencingElements
Array.prototype.forEach.call(properties, (property: string) => {
// :NOTE: using a substring match attr selector here to deal with IE
// "adding extra quotes in url() attrs".
referencingElements = svg.querySelectorAll(
'[' + property + '*="' + currentId + '"]'
)
for (
let b = 0, referencingElementLen = referencingElements.length;
b < referencingElementLen;
b++
) {
const attrValue: string | null =
referencingElements[b].getAttribute(property)
if (
attrValue &&
!attrValue.match(new RegExp('url\\("?#' + currentId + '"?\\)'))
) {
continue
}
referencingElements[b].setAttribute(
property,
'url(#' + newId + ')'
)
}
})

Screenshot from 2023-02-21 14-41-27

Thanks in advance!

Can't import in browser

For ESM module, you currently have this:

import SVGInjector from './svg-injector';
export default SVGInjector;

While this can be resolved to /svg-injector.js, when using as ES6 module, the browser can't resolve and won't import the library.

Please consider adding .js to all your imports, either manually or through babel.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.