vue-svg-icons.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <template>
  2. <svg version="1.1"
  3. :class="klass"
  4. :role="label ? 'img' : 'presentation'"
  5. :aria-label="label"
  6. :viewBox="box"
  7. :style="style">
  8. <slot v-if="!backgroundImage">
  9. <template v-if="icon && icon.paths">
  10. <path v-if="icon && icon.paths" v-for="(path, i) in icon.paths" :key="`path-${i}`" v-bind="path"/>
  11. </template>
  12. <template v-if="icon && icon.polygons">
  13. <polygon v-for="(polygon, i) in icon.polygons" :key="`polygon-${i}`" v-bind="polygon"/>
  14. </template>
  15. <template v-if="icon && icon.raw"><g v-html="raw" v-bind="icon.g"></g></template>
  16. </slot>
  17. </svg>
  18. </template>
  19. <style>
  20. .fa-icon {
  21. display: inline-block;
  22. fill: currentColor;
  23. }
  24. .fa-flip-h {
  25. transform: scale(-1, 1);
  26. }
  27. .fa-flip-v {
  28. transform: scale(1, -1);
  29. }
  30. .fa-flip-vh {
  31. transform: scale(-1, -1);
  32. }
  33. .fa-spin {
  34. animation: fa-spin 0.5s 0s infinite linear;
  35. }
  36. .fa-pulse {
  37. animation: fa-spin 1s infinite steps(8);
  38. }
  39. @keyframes fa-spin {
  40. 0% {
  41. transform: rotate(0deg);
  42. }
  43. 100% {
  44. transform: rotate(360deg);
  45. }
  46. }
  47. </style>
  48. <script>
  49. let icons = {}
  50. export default {
  51. name: 'fa-icon',
  52. props: {
  53. name: {
  54. type: String,
  55. validator (val) {
  56. if (val && !(val in icons)) {
  57. console.warn(`Invalid prop: prop "name" is referring to an unregistered icon "${val}".` +
  58. `\nPlease make sure you have imported this icon before using it.`)
  59. return false
  60. }
  61. return true
  62. }
  63. },
  64. width: [Number, String],
  65. height: [Number, String],
  66. spin: Boolean,
  67. pulse: Boolean,
  68. flip: {
  69. validator (val) {
  70. return val === 'h' || val === 'v' || val === 'vh'
  71. }
  72. },
  73. label: String,
  74. backgroundImage: Boolean,
  75. color: String
  76. },
  77. data () {
  78. return {
  79. }
  80. },
  81. computed: {
  82. klass () {
  83. return {
  84. 'fa-icon': true,
  85. 'fa-spin': this.spin,
  86. 'fa-flip-h': this.flip === 'h',
  87. 'fa-flip-v': this.flip === 'v',
  88. 'fa-flip-vh': this.flip === 'vh',
  89. 'fa-pulse': this.pulse,
  90. [this.$options.name]: true
  91. }
  92. },
  93. icon () {
  94. if (this.name) {
  95. return icons[this.name]
  96. }
  97. return null
  98. },
  99. box () {
  100. if (this.icon) {
  101. return `0 0 ${this.icon.width} ${this.icon.height}`
  102. }
  103. return `0 0 ${this.width} ${this.height}`
  104. },
  105. style () {
  106. if (this.backgroundImage) {
  107. let content = ''
  108. if (this.icon && this.icon.paths) {
  109. for (let path of this.icon.paths) {
  110. let str = ''
  111. for (let k in path) {
  112. str += `${k}='${path[k]}' `
  113. }
  114. content += `<path ${str.trim()}/>`
  115. }
  116. }
  117. if (this.icon && this.icon.polygons) {
  118. for (let path of this.icon.polygons) {
  119. let str = ''
  120. for (let k in path) {
  121. str += `${k}='${path[k]}' `
  122. }
  123. content += `<polygon ${str.trim()}/>`
  124. }
  125. }
  126. if (this.icon && this.icon.raw) {
  127. let str = ''
  128. for (let k in this.icon.g) {
  129. str += `${k}='${this.icon.g[k]}' `
  130. }
  131. content += `<g ${str.trim()}>${this.raw.replace(/"/g, '\'')}</g>`
  132. }
  133. let code = {
  134. '%': '%25',
  135. '!': '%21',
  136. '@': '%40',
  137. '&': '%26',
  138. '#': '%23'
  139. }
  140. let svg = `<svg viewBox='${this.box}' fill='${this.color}' version='1.1' xmlns='http://www.w3.org/2000/svg'>${content}</svg>`
  141. for (let k in code) {
  142. svg = svg.replace(new RegExp(k, 'g'), code[k])
  143. }
  144. let css = {
  145. 'background-image': `url("data:image/svg+xml,${svg}")`,
  146. width: this.width,
  147. height: this.height
  148. }
  149. return css
  150. }
  151. return { color: this.color, width: this.width, height: this.height }
  152. },
  153. raw () {
  154. // generate unique id for each icon's SVG element with ID
  155. if (!this.icon || !this.icon.raw) {
  156. return null
  157. }
  158. let raw = this.icon.raw
  159. let ids = {}
  160. raw = raw.replace(/\s(?:xml:)?id=(["']?)([^"')\s]+)\1/g, (match, quote, id) => {
  161. let uniqueId = getId()
  162. ids[id] = uniqueId
  163. return ` id="${uniqueId}"`
  164. })
  165. raw = raw.replace(/#(?:([^'")\s]+)|xpointer\(id\((['"]?)([^')]+)\2\)\))/g, (match, rawId, _, pointerId) => {
  166. let id = rawId || pointerId
  167. if (!id || !ids[id]) {
  168. return match
  169. }
  170. return `#${ids[id]}`
  171. })
  172. return raw
  173. }
  174. },
  175. mounted () {
  176. if (!this.name) {
  177. console.warn(`Invalid prop: prop "name" is required.`)
  178. return
  179. }
  180. if (!this.icon) {
  181. console.warn(`Invalid icon: prop "name" is not registed.`)
  182. }
  183. },
  184. register (data) {
  185. for (let name in data) {
  186. let icon = data[name]
  187. if (!icon.paths) {
  188. icon.paths = []
  189. }
  190. if (icon.d) {
  191. icon.paths.push({ d: icon.d })
  192. }
  193. if (!icon.polygons) {
  194. icon.polygons = []
  195. }
  196. if (icon.points) {
  197. icon.polygons.push({ points: icon.points })
  198. }
  199. icons[name] = icon
  200. }
  201. },
  202. icons
  203. }
  204. let cursor = 0xd4937
  205. function getId () {
  206. return `fa-${(cursor++).toString(16)}`
  207. }
  208. </script>