jquery-asGradient.es.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. /**
  2. * jQuery asGradient v0.3.3
  3. * https://github.com/amazingSurge/jquery-asGradient
  4. *
  5. * Copyright (c) amazingSurge
  6. * Released under the LGPL-3.0 license
  7. */
  8. import $ from 'jquery';
  9. import Color from 'jquery-asColor';
  10. var DEFAULTS = {
  11. prefixes: ['-webkit-', '-moz-', '-ms-', '-o-'],
  12. forceStandard: true,
  13. angleUseKeyword: true,
  14. emptyString: '',
  15. degradationFormat: false,
  16. cleanPosition: true,
  17. color: {
  18. format: false, // rgb, rgba, hsl, hsla, hex
  19. hexUseName: false,
  20. reduceAlpha: true,
  21. shortenHex: true,
  22. zeroAlphaAsTransparent: false,
  23. invalidValue: {
  24. r: 0,
  25. g: 0,
  26. b: 0,
  27. a: 1
  28. }
  29. }
  30. };
  31. /* eslint no-extend-native: "off" */
  32. if (!String.prototype.includes) {
  33. String.prototype.includes = function(search, start) {
  34. 'use strict';
  35. if (typeof start !== 'number') {
  36. start = 0;
  37. }
  38. if (start + search.length > this.length) {
  39. return false;
  40. }
  41. return this.indexOf(search, start) !== -1;
  42. };
  43. }
  44. function getPrefix() {
  45. const ua = window.navigator.userAgent;
  46. let prefix = '';
  47. if (/MSIE/g.test(ua)) {
  48. prefix = '-ms-';
  49. } else if (/Firefox/g.test(ua)) {
  50. prefix = '-moz-';
  51. } else if (/(WebKit)/i.test(ua)) {
  52. prefix = '-webkit-';
  53. } else if (/Opera/g.test(ua)) {
  54. prefix = '-o-';
  55. }
  56. return prefix;
  57. }
  58. function flip(o) {
  59. const flipped = {};
  60. for (const i in o) {
  61. if (o.hasOwnProperty(i)) {
  62. flipped[o[i]] = i;
  63. }
  64. }
  65. return flipped;
  66. }
  67. function reverseDirection(direction) {
  68. const mapping = {
  69. 'top': 'bottom',
  70. 'right': 'left',
  71. 'bottom': 'top',
  72. 'left': 'right',
  73. 'right top': 'left bottom',
  74. 'top right': 'bottom left',
  75. 'bottom right': 'top left',
  76. 'right bottom': 'left top',
  77. 'left bottom': 'right top',
  78. 'bottom left': 'top right',
  79. 'top left': 'bottom right',
  80. 'left top': 'right bottom'
  81. };
  82. return mapping.hasOwnProperty(direction) ? mapping[direction] : direction;
  83. }
  84. function isDirection(n) {
  85. const reg = /^(top|left|right|bottom)$/i;
  86. return reg.test(n);
  87. }
  88. var keywordAngleMap = {
  89. 'to top': 0,
  90. 'to right': 90,
  91. 'to bottom': 180,
  92. 'to left': 270,
  93. 'to right top': 45,
  94. 'to top right': 45,
  95. 'to bottom right': 135,
  96. 'to right bottom': 135,
  97. 'to left bottom': 225,
  98. 'to bottom left': 225,
  99. 'to top left': 315,
  100. 'to left top': 315
  101. };
  102. const angleKeywordMap = flip(keywordAngleMap);
  103. const RegExpStrings = (() => {
  104. const color = /(?:rgba|rgb|hsla|hsl)\s*\([\s\d\.,%]+\)|#[a-z0-9]{3,6}|[a-z]+/i;
  105. const position = /\d{1,3}%/i;
  106. const angle = /(?:to ){0,1}(?:(?:top|left|right|bottom)\s*){1,2}|\d+deg/i;
  107. const stop = new RegExp(`(${color.source})\\s*(${position.source}){0,1}`, 'i');
  108. const stops = new RegExp(stop.source, 'gi');
  109. const parameters = new RegExp(`(?:(${angle.source})){0,1}\\s*,{0,1}\\s*(.*?)\\s*`, 'i');
  110. const full = new RegExp(`^(-webkit-|-moz-|-ms-|-o-){0,1}(linear|radial|repeating-linear)-gradient\\s*\\(\\s*(${parameters.source})\\s*\\)$`, 'i');
  111. return {
  112. FULL: full,
  113. ANGLE: angle,
  114. COLOR: color,
  115. POSITION: position,
  116. STOP: stop,
  117. STOPS: stops,
  118. PARAMETERS: new RegExp(`^${parameters.source}$`, 'i')
  119. };
  120. })();
  121. var GradientString = {
  122. matchString: function(string) {
  123. const matched = this.parseString(string);
  124. if(matched && matched.value && matched.value.stops && matched.value.stops.length > 1){
  125. return true;
  126. }
  127. return false;
  128. },
  129. parseString: function(string) {
  130. string = $.trim(string);
  131. let matched;
  132. if ((matched = RegExpStrings.FULL.exec(string)) !== null) {
  133. let value = this.parseParameters(matched[3]);
  134. return {
  135. prefix: (typeof matched[1] === 'undefined') ? null : matched[1],
  136. type: matched[2],
  137. value: value
  138. };
  139. } else {
  140. return false;
  141. }
  142. },
  143. parseParameters: function(string) {
  144. let matched;
  145. if ((matched = RegExpStrings.PARAMETERS.exec(string)) !== null) {
  146. let stops = this.parseStops(matched[2]);
  147. return {
  148. angle: (typeof matched[1] === 'undefined') ? 0 : matched[1],
  149. stops: stops
  150. };
  151. } else {
  152. return false;
  153. }
  154. },
  155. parseStops: function(string) {
  156. let matched;
  157. const result = [];
  158. if ((matched = string.match(RegExpStrings.STOPS)) !== null) {
  159. $.each(matched, (i, item) => {
  160. const stop = this.parseStop(item);
  161. if (stop) {
  162. result.push(stop);
  163. }
  164. });
  165. return result;
  166. } else {
  167. return false;
  168. }
  169. },
  170. formatStops: function(stops, cleanPosition) {
  171. let stop;
  172. const output = [];
  173. let positions = [];
  174. const colors = [];
  175. let position;
  176. for (let i = 0; i < stops.length; i++) {
  177. stop = stops[i];
  178. if (typeof stop.position === 'undefined' || stop.position === null) {
  179. if (i === 0) {
  180. position = 0;
  181. } else if (i === stops.length - 1) {
  182. position = 1;
  183. } else {
  184. position = undefined;
  185. }
  186. } else {
  187. position = stop.position;
  188. }
  189. positions.push(position);
  190. colors.push(stop.color.toString());
  191. }
  192. positions = ((data => {
  193. let start = null;
  194. let average;
  195. for (let i = 0; i < data.length; i++) {
  196. if (isNaN(data[i])) {
  197. if (start === null) {
  198. start = i;
  199. continue;
  200. }
  201. } else if (start) {
  202. average = (data[i] - data[start - 1]) / (i - start + 1);
  203. for (let j = start; j < i; j++) {
  204. data[j] = data[start - 1] + (j - start + 1) * average;
  205. }
  206. start = null;
  207. }
  208. }
  209. return data;
  210. }))(positions);
  211. for (let x = 0; x < stops.length; x++) {
  212. if (cleanPosition && ((x === 0 && positions[x] === 0) || (x === stops.length - 1 && positions[x] === 1))) {
  213. position = '';
  214. } else {
  215. position = ` ${this.formatPosition(positions[x])}`;
  216. }
  217. output.push(colors[x] + position);
  218. }
  219. return output.join(', ');
  220. },
  221. parseStop: function(string) {
  222. let matched;
  223. if ((matched = RegExpStrings.STOP.exec(string)) !== null) {
  224. let position = this.parsePosition(matched[2]);
  225. return {
  226. color: matched[1],
  227. position: position
  228. };
  229. } else {
  230. return false;
  231. }
  232. },
  233. parsePosition: function(string) {
  234. if (typeof string === 'string' && string.substr(-1) === '%') {
  235. string = parseFloat(string.slice(0, -1) / 100);
  236. }
  237. if(typeof string !== 'undefined' && string !== null) {
  238. return parseFloat(string, 10);
  239. } else {
  240. return null;
  241. }
  242. },
  243. formatPosition: function(value) {
  244. return `${parseInt(value * 100, 10)}%`;
  245. },
  246. parseAngle: function(string, notStandard) {
  247. if (typeof string === 'string' && string.includes('deg')) {
  248. string = string.replace('deg', '');
  249. }
  250. if (!isNaN(string)) {
  251. if (notStandard) {
  252. string = this.fixOldAngle(string);
  253. }
  254. }
  255. if (typeof string === 'string') {
  256. const directions = string.split(' ');
  257. const filtered = [];
  258. for (const i in directions) {
  259. if (isDirection(directions[i])) {
  260. filtered.push(directions[i].toLowerCase());
  261. }
  262. }
  263. let keyword = filtered.join(' ');
  264. if (!string.includes('to ')) {
  265. keyword = reverseDirection(keyword);
  266. }
  267. keyword = `to ${keyword}`;
  268. if (keywordAngleMap.hasOwnProperty(keyword)) {
  269. string = keywordAngleMap[keyword];
  270. }
  271. }
  272. let value = parseFloat(string, 10);
  273. if (value > 360) {
  274. value %= 360;
  275. } else if (value < 0) {
  276. value %= -360;
  277. if (value !== 0) {
  278. value += 360;
  279. }
  280. }
  281. return value;
  282. },
  283. fixOldAngle: function(value) {
  284. value = parseFloat(value);
  285. value = Math.abs(450 - value) % 360;
  286. value = parseFloat(value.toFixed(3));
  287. return value;
  288. },
  289. formatAngle: function(value, notStandard, useKeyword) {
  290. value = parseInt(value, 10);
  291. if (useKeyword && angleKeywordMap.hasOwnProperty(value)) {
  292. value = angleKeywordMap[value];
  293. if (notStandard) {
  294. value = reverseDirection(value.substr(3));
  295. }
  296. } else {
  297. if (notStandard) {
  298. value = this.fixOldAngle(value);
  299. }
  300. value = `${value}deg`;
  301. }
  302. return value;
  303. }
  304. };
  305. class ColorStop {
  306. constructor(color, position, gradient) {
  307. this.color = Color(color, gradient.options.color);
  308. this.position = GradientString.parsePosition(position);
  309. this.id = ++gradient._stopIdCount;
  310. this.gradient = gradient;
  311. }
  312. setPosition(string) {
  313. const position = GradientString.parsePosition(string);
  314. if(this.position !== position){
  315. this.position = position;
  316. this.gradient.reorder();
  317. }
  318. }
  319. setColor(string) {
  320. this.color.fromString(string);
  321. }
  322. remove() {
  323. this.gradient.removeById(this.id);
  324. }
  325. }
  326. var GradientTypes = {
  327. LINEAR: {
  328. parse(result) {
  329. return {
  330. r: (result[1].substr(-1) === '%') ? parseInt(result[1].slice(0, -1) * 2.55, 10) : parseInt(result[1], 10),
  331. g: (result[2].substr(-1) === '%') ? parseInt(result[2].slice(0, -1) * 2.55, 10) : parseInt(result[2], 10),
  332. b: (result[3].substr(-1) === '%') ? parseInt(result[3].slice(0, -1) * 2.55, 10) : parseInt(result[3], 10),
  333. a: 1
  334. };
  335. },
  336. to(gradient, instance, prefix) {
  337. if (gradient.stops.length === 0) {
  338. return instance.options.emptyString;
  339. }
  340. if (gradient.stops.length === 1) {
  341. return gradient.stops[0].color.to(instance.options.degradationFormat);
  342. }
  343. let standard = instance.options.forceStandard;
  344. let _prefix = instance._prefix;
  345. if (!_prefix) {
  346. standard = true;
  347. }
  348. if (prefix && -1 !== $.inArray(prefix, instance.options.prefixes)) {
  349. standard = false;
  350. _prefix = prefix;
  351. }
  352. const angle = GradientString.formatAngle(gradient.angle, !standard, instance.options.angleUseKeyword);
  353. const stops = GradientString.formatStops(gradient.stops, instance.options.cleanPosition);
  354. const output = `linear-gradient(${angle}, ${stops})`;
  355. if (standard) {
  356. return output;
  357. } else {
  358. return _prefix + output;
  359. }
  360. }
  361. }
  362. };
  363. class AsGradient {
  364. constructor(string, options) {
  365. if (typeof string === 'object' && typeof options === 'undefined') {
  366. options = string;
  367. string = undefined;
  368. }
  369. this.value = {
  370. angle: 0,
  371. stops: []
  372. };
  373. this.options = $.extend(true, {}, DEFAULTS, options);
  374. this._type = 'LINEAR';
  375. this._prefix = null;
  376. this.length = this.value.stops.length;
  377. this.current = 0;
  378. this._stopIdCount = 0;
  379. this.init(string);
  380. }
  381. init(string) {
  382. if (string) {
  383. this.fromString(string);
  384. }
  385. }
  386. val(value) {
  387. if (typeof value === 'undefined') {
  388. return this.toString();
  389. } else {
  390. this.fromString(value);
  391. return this;
  392. }
  393. }
  394. angle(value) {
  395. if (typeof value === 'undefined') {
  396. return this.value.angle;
  397. } else {
  398. this.value.angle = GradientString.parseAngle(value);
  399. return this;
  400. }
  401. }
  402. append(color, position) {
  403. return this.insert(color, position, this.length);
  404. }
  405. reorder() {
  406. if(this.length < 2){
  407. return;
  408. }
  409. this.value.stops = this.value.stops.sort((a, b) => a.position - b.position);
  410. }
  411. insert(color, position, index) {
  412. if (typeof index === 'undefined') {
  413. index = this.current;
  414. }
  415. const stop = new ColorStop(color, position, this);
  416. this.value.stops.splice(index, 0, stop);
  417. this.length = this.length + 1;
  418. this.current = index;
  419. return stop;
  420. }
  421. getById(id) {
  422. if(this.length > 0){
  423. for(const i in this.value.stops){
  424. if(id === this.value.stops[i].id){
  425. return this.value.stops[i];
  426. }
  427. }
  428. }
  429. return false;
  430. }
  431. removeById(id) {
  432. const index = this.getIndexById(id);
  433. if(index){
  434. this.remove(index);
  435. }
  436. }
  437. getIndexById(id) {
  438. let index = 0;
  439. for(const i in this.value.stops){
  440. if(id === this.value.stops[i].id){
  441. return index;
  442. }
  443. index ++;
  444. }
  445. return false;
  446. }
  447. getCurrent() {
  448. return this.value.stops[this.current];
  449. }
  450. setCurrentById(id) {
  451. let index = 0;
  452. for(const i in this.value.stops){
  453. if(this.value.stops[i].id !== id){
  454. index ++;
  455. } else {
  456. this.current = index;
  457. }
  458. }
  459. }
  460. get(index) {
  461. if (typeof index === 'undefined') {
  462. index = this.current;
  463. }
  464. if (index >= 0 && index < this.length) {
  465. this.current = index;
  466. return this.value.stops[index];
  467. } else {
  468. return false;
  469. }
  470. }
  471. remove(index) {
  472. if (typeof index === 'undefined') {
  473. index = this.current;
  474. }
  475. if (index >= 0 && index < this.length) {
  476. this.value.stops.splice(index, 1);
  477. this.length = this.length - 1;
  478. this.current = index - 1;
  479. }
  480. }
  481. empty() {
  482. this.value.stops = [];
  483. this.length = 0;
  484. this.current = 0;
  485. }
  486. reset() {
  487. this.value._angle = 0;
  488. this.empty();
  489. this._prefix = null;
  490. this._type = 'LINEAR';
  491. }
  492. type(type) {
  493. if (typeof type === 'string' && (type = type.toUpperCase()) && typeof GradientTypes[type] !== 'undefined') {
  494. this._type = type;
  495. return this;
  496. } else {
  497. return this._type;
  498. }
  499. }
  500. fromString(string) {
  501. this.reset();
  502. const result = GradientString.parseString(string);
  503. if (result) {
  504. this._prefix = result.prefix;
  505. this.type(result.type);
  506. if (result.value) {
  507. this.value.angle = GradientString.parseAngle(result.value.angle, this._prefix !== null);
  508. $.each(result.value.stops, (i, stop) => {
  509. this.append(stop.color, stop.position);
  510. });
  511. }
  512. }
  513. }
  514. toString(prefix) {
  515. if(prefix === true){
  516. prefix = getPrefix();
  517. }
  518. return GradientTypes[this.type()].to(this.value, this, prefix);
  519. }
  520. matchString(string) {
  521. return GradientString.matchString(string);
  522. }
  523. toStringWithAngle(angle, prefix) {
  524. const value = $.extend(true, {}, this.value);
  525. value.angle = GradientString.parseAngle(angle);
  526. if(prefix === true){
  527. prefix = getPrefix();
  528. }
  529. return GradientTypes[this.type()].to(value, this, prefix);
  530. }
  531. getPrefixedStrings() {
  532. const strings = [];
  533. for (let i in this.options.prefixes) {
  534. if(Object.hasOwnProperty.call(this.options.prefixes, i)){
  535. strings.push(this.toString(this.options.prefixes[i]));
  536. }
  537. }
  538. return strings;
  539. }
  540. static setDefaults(options) {
  541. $.extend(true, DEFAULTS, $.isPlainObject(options) && options);
  542. }
  543. }
  544. var info = {
  545. version:'0.3.3'
  546. };
  547. const OtherAsGradient = $.asGradient;
  548. const jQueryAsGradient = function(...args) {
  549. return new AsGradient(...args);
  550. };
  551. $.asGradient = jQueryAsGradient;
  552. $.asGradient.Constructor = AsGradient;
  553. $.extend($.asGradient, {
  554. setDefaults: AsGradient.setDefaults,
  555. noConflict: function() {
  556. $.asGradient = OtherAsGradient;
  557. return jQueryAsGradient;
  558. }
  559. }, GradientString, info);
  560. var main = $.asGradient;
  561. export default main;