Recientemente fui a la capacitación ReactJS de Michael Jackson y Ryan Florence. Estaba muy emocionado de asistir, en parte porque tenía muchas preguntas sobre SVG y React. Hay muchas partes sobre cómo trabajar con React y SVG, y especialmente manipularlo, que aún no son compatibles. Una de las principales lagunas para mí fue la <use>
elemento, ya que la mayoría de los sistemas de iconos SVG están construidos con <use>
.
Le pregunté a Michael si pensaba que podría haber un mejor soporte para algunas de estas funciones, pero me mostró una forma mucho mejor de trabajar con ellas, eludiendo este método por completo. Repasaremos esta técnica para que pueda comenzar a escribir sistemas de iconos SVG escalables en React, así como algunos trucos que propongo que también podrían funcionar bien.
Nota: Vale la pena decir que la compatibilidad con el uso se mejoró recientemente, pero he notado que, en el mejor de los casos, es irregular y hay otros problemas de enrutamiento y XML. Le mostraremos otra forma más limpia aquí.
Qué es <use>
?
Para aquellos que no están familiarizados con cómo se construyen típicamente los sistemas de iconos SVG, funciona un poco así. El <use>
elemento clona una copia de cualquier otro elemento de forma SVG con el ID al que hace referencia en el xlink:href
atributo, y aún manipularlo sin reiterar todos los datos de la ruta. Quizás se pregunte por qué uno no usaría simplemente un SVG como <img>
etiqueta. Podrías hacerlo, pero cada ícono sería una solicitud individual y no tendrías acceso a cambiar partes del SVG, como el fill
color.
Utilizando <use>
nos permite mantener los datos de la ruta y la apariencia básica de nuestros íconos definidos en un solo lugar para que puedan actualizarse una vez y cambiar en todas partes, al mismo tiempo que nos brinda el beneficio de actualizarlos sobre la marcha.
Joni Trythall tiene un excelente artículo sobre el uso y los íconos SVG, y Chris Coyier también escribió otro artículo asombroso sobre CSS-Tricks.
Aquí hay un pequeño ejemplo si desea ver cómo se ve el marcado:
Vea el lápiz bc5441283414ae5085f3c19e2fd3f7f2 de Sarah Drasner (@sdras) en CodePen.
¿Por qué molestarse con los iconos SVG?
Algunos de ustedes en este punto se estarán preguntando por qué usaríamos un sistema de íconos SVG en lugar de una fuente de íconos para empezar. Tenemos nuestra propia comparación sobre ese tema. Además, hay un montón de personas que escriben y hablan sobre esto en este momento.
Estas son algunas de las razones más convincentes, en mi opinión:
- Las fuentes de iconos son difíciles de hacer accesibles. SVG tiene la capacidad de agregar títulos y etiquetas ARIA, que brindan una gran ventaja a la accesibilidad, particularmente en los casos en que el ícono está solo y es una única fuente de navegación informativa. Piensa: personas ciegas, disléxicas, ancianos (también serás anciano algún día, con suerte, así que si no eres el tipo de desarrollador que se preocupa por este subconjunto, ¡hazlo por el karma! Pero en serio, cuida a los ancianos. )
- Las fuentes de iconos no son tan nítidas en algunas pantallas. Puede evitar esto haciendo un elegante suavizado de fuentes en CSS, pero he notado una advertencia: es difícil anular sin desactivar por completo el suavizado de fuentes. Los SVG son más nítidos en general, el dibujo es para lo que están diseñados.
- Las fuentes de iconos fallan la mayor parte del tiempo. La mayoría de los desarrolladores que conozco se han encontrado con escenarios en los que falta el glifo X en un cuadro, hay muchas formas en que las fuentes de iconos pueden fallar donde los SVG no lo hacen. Ya sean problemas CORS o Opera mini, es un dolor de cabeza.
- Las fuentes de iconos son difíciles de ubicar. Son una imagen que estás posicionando con estilos de fuente. ‘Nuff dijo. No puede animar piezas de ellos sin apilarlos. Los SVG ofrecen un DOM navegable para animar partes de un icono o colorear secciones. No todo el mundo querría hacer esto, pero seguro que es bueno tener la opción.
Si eres como yo y estás actualizando una enorme base de código, donde para pasar de una fuente de íconos a SVG tendrías que actualizar literalmente cientos de instancias de marcado, lo entiendo. Hago. Puede que no valga la pena el tiempo en ese caso. Pero si está reescribiendo sus puntos de vista y actualizándolos con React, vale la pena volver a visitar una oportunidad aquí.
Tl; dr: no es necesario <use>
en React
Después de que Michael me escuchó pacientemente explicar cómo usamos <use>
y me hizo mostrarle un sistema de iconos de ejemplo, su solución fue simple: no es realmente necesario.
Considere esto: la única razón por la que estábamos definiendo íconos para luego reutilizarlos (generalmente como <symbol>
pecado <defs>
) fue para que no tuviéramos que repetirnos y pudiéramos actualizar las rutas SVG en un solo lugar. Pero React ya lo permite. Simplemente creamos el componente:
// Icon
const IconUmbrella = React.createClass({
render() {
return (
<svg className="umbrella" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
<title id="title">Umbrella Icon</title>
<path d="M27 14h5c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0zM27 14c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0 14c0 1.112-0.895 2-2 2-1.112 0-2-0.896-2-2.001v-1.494c0-0.291 0.224-0.505 0.5-0.505 0.268 0 0.5 0.226 0.5 0.505v1.505c0 0.547 0.444 0.991 1 0.991 0.552 0 1-0.451 1-0.991v-14.009c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-5.415 6.671-9.825 15-9.995v-1.506c0-0.283 0.224-0.499 0.5-0.499 0.268 0 0.5 0.224 0.5 0.499v1.506c8.329 0.17 15 4.58 15 9.995h-5z"/>
</svg>
)
}
});
// which makes this reusable component for other views
<IconUmbrella />
Vea el icono de lápiz SVG en React por Sarah Drasner (@sdras) en CodePen.
Y podemos usarlo una y otra vez, pero a diferencia de los más antiguos <use>
De esta manera, no tenemos una solicitud HTTP adicional.
Dos cosas similares a SVG que puede notar en el ejemplo anterior. Uno, no tengo este tipo de salida:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
O incluso esto en la propia etiqueta SVG:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" …
Eso es porque me aseguré de optimizar mis SVG con SVGOMG o SVGO antes de agregar el marcado en todas partes. Le sugiero que lo haga también, ya que puede reducir el tamaño de su SVG en una cantidad respetable. Normalmente veo porcentajes de alrededor del 30%, pero puedo llegar al 60% o más.
Otra cosa que puede notar es que estoy agregando un título y una etiqueta ARIA. Esto ayudará a los lectores de pantalla a hablar el ícono para las personas que usan tecnologías de asistencia.
<svg className="umbrella" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
<title id="title">Umbrella Icon</title>
Dado que esta identificación tiene que ser única, podemos hacer accesorios de paso a nuestras instancias del ícono y se propagará tanto al título como a la etiqueta aria de la siguiente manera:
// App
const App = React.createClass({
render() {
return (
<div>
<div className="switcher">
<IconOffice iconTitle="animatedOffice" />
</div>
<IconOffice iconTitle="orangeBook" bookfill="orange" bookside="#39B39B" bookfront="#76CEBD"/>
<IconOffice iconTitle="biggerOffice" width="200" height="200"/>
</div>
)
}
});
// Icon
const IconOffice = React.createClass({
...
render() {
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg" width={this.props.width} height={this.props.height} viewBox="0 0 188.5 188.5" aria-labelledby={this.props.iconTitle}>
<title id={this.props.iconTitle}>Office With a Lamp</title>
...
</svg>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
La mejor parte, quizás
Aquí hay una parte realmente interesante de todo esto: además de no necesitar solicitudes HTTP adicionales, también puedo actualizar completamente la forma del SVG en el futuro sin necesidad de cambios de marcado, ya que el componente es autónomo. Incluso mejor que eso, no necesito cargar toda la fuente del icono (o sprite SVG) en cada página. Con todos los íconos en componentes, puedo usar algo como el paquete web para “optar por participar” en los íconos que necesito para una vista determinada. Con el peso de las fuentes y los glifos de fuentes de iconos particularmente pesados, esa es una gran posibilidad de una gran ventaja en el rendimiento.
Todo eso, más: podemos mutar partes del ícono sobre la marcha con color o animación de una manera muy simple con SVG y accesorios.
Mutandolo sobre la marcha
Una cosa aquí que quizás hayas notado es que aún no lo estamos ajustando sobre la marcha, lo cual es parte de la razón por la que usamos SVG en primer lugar, ¿verdad? Podemos declarar algunos accesorios predeterminados en el icono y luego cambiarlos, así:
// App
const App = React.createClass({
render() {
return (
<div>
<IconOffice />
<IconOffice width="200" height="200"/>
</div>
)
}
});
// Icon
const IconOffice = React.createClass({
getDefaultProps() {
return {
width: '100',
height: '200'
};
},
render() {
return (
<svg className="office" width={this.props.width} height={this.props.height} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 188.5 188.5" aria-labelledby="title">
<title id="title">Office Icon</title>
...
</svg>
)
}
});
ReactDOM.render(<App />, document.querySelector("#main"));
Vea el icono de lápiz SVG en React con accesorios predeterminados de Sarah Drasner (@sdras) en CodePen.
Vayamos un paso más allá y cambiemos parte de la apariencia según la instancia. Nosotros podemos usar props
para esto, y declare algunos accesorios predeterminados.
Me encanta SVG porque ahora tenemos un DOM navegable, así que a continuación cambiemos el color de varias formas sobre la marcha con fill
. Tenga en cuenta que si está acostumbrado a trabajar con fuentes de iconos, ya no cambiará el color con color
, sino más bien con fill
en lugar de. Puede consultar el segundo ejemplo a continuación para ver esto en acción, los libros han cambiado de color. También me encanta la capacidad de animar estas piezas sobre la marcha, a continuación lo hemos envuelto en un div para animarlo muy fácilmente con CSS (es posible que deba presionar volver a ejecutar para ver la reproducción de la animación):
Vea el ícono Pen SVG en React con los accesorios predeterminados y la animación de Sarah Drasner (@sdras) en CodePen.
// App
const App = React.createClass({
render() {
return (
<div>
<div className="switcher">
<IconOffice />
</div>
<IconOffice bookfill="orange" bookside="#39B39B" bookfront="#76CEBD" />
<IconOffice width="200" height="200" />
</div>
)
}
});
// Icon
const IconOffice = React.createClass({
getDefaultProps() {
return {
width: '100',
height: '200',
bookfill: '#f77b55',
bookside: '#353f49',
bookfront: '#474f59'
};
},
render() {
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg" width={this.props.width} height={this.props.height} viewBox="0 0 188.5 188.5" aria-labelledby="title">
<title id="title">Office Icon</title>
<g className="cls-2">
<circle id="background" className="cls-3" cx="94.2" cy="94.2" r="94.2"/>
<path className="cls-4" d="M50.3 69.8h10.4v72.51H50.3z"/>
<path fill={this.props.bookside} d="M50.3 77.5h10.4v57.18H50.3z"/>
<path fill={this.props.bookfront} d="M60.7 77.5h38.9v57.19H60.7z"/>
<path className="cls-7" d="M60.7 69.8h38.9v7.66H60.7z"/>
<path className="cls-5" d="M60.7 134.7h38.9v7.66H60.7z"/>
...
</svg>
)
}
});
ReactDOM.render(<App />, document.querySelector("#main"));
.switcher .office {
#bulb { animation: switch 3s 4 ease both; }
#background { animation: fillChange 3s 4 ease both; }
}
@keyframes switch {
50% {
opacity: 1;
}
}
@keyframes fillChange {
50% {
fill: #FFDB79;
}
}
Uno de mis increíbles compañeros de trabajo en Trulia, Mattia Toso, también recomendó una forma realmente agradable y mucho más limpia de declarar todos estos accesorios. Podemos reducir la repetición del this.props
aquí declarando const para todos nuestros usos, y luego simplemente aplique la variable en su lugar:
render() {
const { height, width, bookfill, bookside, bookfront } = this.props;
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 188.5 188.5" aria-labelledby="title">
<title id="title">Office Icon</title>
<g className="cls-2">
<circle id="background" className="cls-3" cx="94.2" cy="94.2" r="94.2"/>
<path className="cls-4" d="M50.3 69.8h10.4v72.51H50.3z"/>
<path fill={bookside} d="M50.3 77.5h10.4v57.18H50.3z"/>
<path fill={bookfront} d="M60.7 77.5h38.9v57.19H60.7z"/>
También podemos hacer que esto sea aún más asombroso declarando propTypes
en los accesorios que estamos usando. Los PropTypes son muy útiles porque son como documentos vivientes para los accesorios que estamos reutilizando.
propTypes: {
width: string,
height: string,
bookfill: string,
bookside: string,
bookfront: string
},
De esa manera, si los usamos incorrectamente, como en el ejemplo a continuación, obtendremos un error de consola que no detendrá la ejecución de nuestro código, pero alerta a otras personas con las que podríamos estar colaborando (o con nosotros mismos) que estamos usando accesorios incorrectamente. . Aquí, estoy usando un número en lugar de una cadena para mis accesorios.
<IconOffice bookfill={200} bookside="#39B39B" bookfront="#76CEBD" />
Y me sale el siguiente error:
Vea el ícono Pen SVG en React con propagación con error de Sarah Drasner (@sdras) en CodePen.
Aún más delgado con React 0.14+
En las versiones más nuevas de React, podemos reducir algo de este problema y simplificar nuestro código aún más, pero solo si es un componente muy “tonto”, por ejemplo, no toma métodos de ciclo de vida. Los iconos son un caso de uso bastante bueno para esto, ya que en su mayoría solo estamos renderizando, así que probémoslo. Podemos deshacernos de React.createClass
y escribimos nuestros componentes como funciones simples. Esto es bastante bueno si ha estado usando JavaScript durante mucho tiempo pero está menos familiarizado con React, se lee como las funciones a las que todos estamos acostumbrados. Limpiemos aún más nuestros accesorios y reutilicemos el ícono del paraguas tal como lo haríamos en un sitio web.
// App
function App() {
return (
<div>
<Header />
<IconUmbrella />
<IconUmbrella umbrellafill="#333" />
<IconUmbrella umbrellafill="#ccc" />
</div>
)
}
// Header
function Header() {
return (
<h3>Hello, world!</h3>
)
}
// Icon
function IconUmbrella(props) {
const umbrellafill = props.umbrellafill || 'orangered'
return (
<svg className="umbrella" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
<title id="title">Umbrella</title>
<path fill={umbrellafill} d="M27 14h5c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0zM27 14c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2v0 14c0 1.112-0.895 2-2 2-1.112 0-2-0.896-2-2.001v-1.494c0-0.291 0.224-0.505 0.5-0.505 0.268 0 0.5 0.226 0.5 0.505v1.505c0 0.547 0.444 0.991 1 0.991 0.552 0 1-0.451 1-0.991v-14.009c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-1.105-1.119-2-2.5-2s-2.5 0.895-2.5 2c0-5.415 6.671-9.825 15-9.995v-1.506c0-0.283 0.224-0.499 0.5-0.499 0.268 0 0.5 0.224 0.5 0.499v1.506c8.329 0.17 15 4.58 15 9.995h-5z"/>
</svg>
)
}
ReactDOM.render(<App />, document.querySelector("#main"));
Vea el icono de lápiz SVG en React por Sarah Drasner (@sdras) en CodePen.
Los sistemas de iconos SVG son maravillosamente simples y fácilmente ampliables en React, tienen menos solicitudes HTTP y son fáciles de mantener en el futuro, debido al hecho de que podemos actualizar completamente la salida en el futuro sin ningún cambio de marcado repetitivo. Podemos aumentar el rendimiento eligiendo exactamente lo que necesitamos. Podemos cambiarlos sobre la marcha con accesorios de color e incluso agregar animación CSS. Todo esto, y también podemos hacerlos accesibles para lectores de pantalla, lo que hace que los sistemas de iconos React y SVG sean una forma realmente agradable de agregar iconos a las vistas en la web.