From f5bc7b4ea2450d6a117d9c66792ccb3055c1bba1 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Thu, 7 Apr 2022 17:23:58 +0300 Subject: [PATCH] Render icons as background in SVG framework --- packages/iconify/demo/usage.html | 4 +- packages/iconify/src/render/bg.ts | 119 ++++++++++++++++++++++++++ packages/iconify/src/scanner/index.ts | 4 +- 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 packages/iconify/src/render/bg.ts diff --git a/packages/iconify/demo/usage.html b/packages/iconify/demo/usage.html index 050f90b..928b4ee 100644 --- a/packages/iconify/demo/usage.html +++ b/packages/iconify/demo/usage.html @@ -45,12 +45,14 @@

This is a simple HTML page with few icons.

- Icon that behaves like an image: + Icons that behave like images: +

Icon that behaves like an glyph (aligned to bottom of this text): +

Changing icon color: diff --git a/packages/iconify/src/render/bg.ts b/packages/iconify/src/render/bg.ts new file mode 100644 index 0000000..7187549 --- /dev/null +++ b/packages/iconify/src/render/bg.ts @@ -0,0 +1,119 @@ +import type { FullIconifyIcon } from '@iconify/utils/lib/icon'; +import { iconToSVG } from '@iconify/utils/lib/svg/build'; +import { + elementDataProperty, + IconifyElement, + IconifyElementProps, + IconifyElementData, +} from '../scanner/config'; +import { applyClasses } from './classes'; +import { applyStyle } from './style'; + +const commonProps: Record = { + display: 'inline-block', +}; + +const monotoneProps: Record = { + '-webkit-mask-image': 'var(--svg)', + '-webkit-mask-repeat': 'no-repeat', + '-webkit-mask-size': '100% 100%', + 'mask-image': 'var(--svg)', + 'mask-repeat': 'no-repeat', + 'mask-size': '100% 100%', + 'background-color': 'currentColor', +}; + +const coloredProps: Record = { + 'background-image': 'var(--svg)', + 'background-repeat': 'no-repeat', + 'background-size': '100% 100%', + 'background-color': 'transparent', +}; + +/** + * Render icon as inline SVG + */ +export function renderBackground( + element: IconifyElement, + props: IconifyElementProps, + iconData: FullIconifyIcon +): IconifyElement { + // Generate data to render + const renderData = iconToSVG(iconData, { + ...props.customisations, + }); + + // Get old data + const oldData = element[elementDataProperty]; + + // Generate SVG + const renderAttribs = renderData.attributes; + let renderAttribsHTML = ''; + for (const attr in renderAttribs) { + const value = ( + iconData[attr] !== void 0 ? iconData[attr] : renderAttribs[attr] + ) as string; + renderAttribsHTML += ' ' + attr + '="' + value + '"'; + } + const html = + '' + + renderData.body + + ''; + + // Add classes + const classesToAdd: Set = new Set(['iconify']); + const iconName = props.icon; + ['provider', 'prefix'].forEach((attr: keyof typeof iconName) => { + if (iconName[attr]) { + classesToAdd.add('iconify--' + iconName[attr]); + } + }); + const addedClasses = applyClasses( + element, + classesToAdd, + new Set(oldData && oldData.addedClasses) + ); + + // Update style + const isMonotone = renderData.body.indexOf('currentColor') !== -1; + const url = + 'url("data:image/svg+xml,' + + html + .replace(/"/g, "'") + .replace(/%/g, '%25') + .replace(/#/g, '%23') + .replace(/{/g, '%7B') + .replace(/}/g, '%7D') + .replace(//g, '%3E') + + '")'; + const newStyles: Record = { + '--svg': url, + 'width': renderAttribs.width, + 'height': renderAttribs.height, + ...commonProps, + ...(isMonotone ? monotoneProps : coloredProps), + }; + if (renderData.inline) { + newStyles['vertical-align'] = '-0.125em'; + } + + const addedStyles = applyStyle( + element, + newStyles, + oldData && oldData.addedStyles + ); + + // Add data to element + const newData: IconifyElementData = { + ...props, + status: 'loaded', + addedClasses, + addedStyles, + }; + element[elementDataProperty] = newData; + + return element; +} diff --git a/packages/iconify/src/scanner/index.ts b/packages/iconify/src/scanner/index.ts index a222341..4266447 100644 --- a/packages/iconify/src/scanner/index.ts +++ b/packages/iconify/src/scanner/index.ts @@ -22,6 +22,7 @@ import { resumeObservingNode, stopObserving, } from '../observer'; +import { renderBackground } from '../render/bg'; /** * Flag to avoid scanning DOM too often @@ -121,7 +122,8 @@ export function scanDOM(rootNode?: ObservedNode, addTempNode = false): void { paused = true; pauseObservingNode(observedNode); } - renderInlineSVG(element, props, iconData); + // renderInlineSVG(element, props, iconData); + renderBackground(element, props, iconData); } // Find all elements