diff --git a/README.md b/README.md index e05cd97..86cf772 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ A CircleType instance creates a circular text element. **Kind**: global class * [CircleType](#CircleType) - * [new CircleType(elem)](#new_CircleType_new) + * [new CircleType(elem, [splitter])](#new_CircleType_new) * [.radius(value)](#CircleType+radius) ⇒ [CircleType](#CircleType) * [.radius()](#CircleType+radius) ⇒ number * [.dir(value)](#CircleType+dir) ⇒ [CircleType](#CircleType) @@ -48,11 +48,12 @@ A CircleType instance creates a circular text element. -### new CircleType(elem) +### new CircleType(elem, [splitter]) | Param | Type | Description | | --- | --- | --- | | elem | HTMLElement | A target HTML element. | +| [splitter] | function | An optional function used to split the element's text content into individual characters | **Example** ```js @@ -61,6 +62,14 @@ const circleType = new CircleType(document.getElementById('myElement')); // Set the text radius and direction. Note: setter methods are chainable. circleType.radius(200).dir(-1); + +// Provide your own splitter function to handle emojis +// @see https://github.com/orling/grapheme-splitter +const splitter = new GraphemeSplitter() +new CircleType( + document.getElementById('myElement'), + splitter.splitGraphemes.bind(splitter) +); ``` diff --git a/assets/stylesheets/screen.css b/assets/stylesheets/screen.css index 8be0ee1..8195d39 100644 --- a/assets/stylesheets/screen.css +++ b/assets/stylesheets/screen.css @@ -51,13 +51,9 @@ h3 { code { display: block; - background: #101010; - color: #fff; - font-family: "Andale Mono", AndaleMono, monospace; - font-size: 12px; - padding: 1em; + font-size: 14px; + padding: 1em !important; margin: 0 0 4em; - white-space: pre; } footer { diff --git a/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_0_phone.png b/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_0_phone.png index 571da71..b7e66f3 100644 Binary files a/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_0_phone.png and b/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_0_phone.png differ diff --git a/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_1_tablet.png b/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_1_tablet.png index 85ae90f..4fd39f5 100644 Binary files a/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_1_tablet.png and b/backstop_data/bitmaps_reference/circletype_CircleType_Demo_6_demo__n6_1_tablet.png differ diff --git a/dist/circletype.min.js b/dist/circletype.min.js index 06e3232..52db06a 100644 --- a/dist/circletype.min.js +++ b/dist/circletype.min.js @@ -1,8 +1,8 @@ /*! - * circletype 2.2.1 + * circletype 2.3.0 * A JavaScript library that lets you curve type on the web. * Copyright © 2014-2018 Peter Hrynkow * Licensed MIT * https://github.com/peterhry/CircleType#readme */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.CircleType=e():t.CircleType=e()}("undefined"!=typeof self?self:this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var n={};return e.m=t,e.c=n,e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=29)}([function(t,e,n){var r=n(24)("wks"),i=n(12),o=n(1).Symbol,u="function"==typeof o;(t.exports=function(t){return r[t]||(r[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=r},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e){var n=t.exports={version:"2.5.6"};"number"==typeof __e&&(__e=n)},function(t,e,n){var r=n(4),i=n(11);t.exports=n(6)?function(t,e,n){return r.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var r=n(5),i=n(34),o=n(35),u=Object.defineProperty;e.f=n(6)?Object.defineProperty:function(t,e,n){if(r(t),e=o(e,!0),r(n),i)try{return u(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){var r=n(10);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,e,n){t.exports=!n(17)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e){t.exports={}},function(t,e,n){var r=n(24)("keys"),i=n(12);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,e){t.exports=!1},function(t,e,n){var r=n(1),i=n(2),o=n(3),u=n(19),c=n(20),f=function(t,e,n){var a,s,l,p,h=t&f.F,d=t&f.G,v=t&f.S,y=t&f.P,_=t&f.B,m=d?r:v?r[e]||(r[e]={}):(r[e]||{}).prototype,g=d?i:i[e]||(i[e]={}),x=g.prototype||(g.prototype={});d&&(n=e);for(a in n)s=!h&&m&&void 0!==m[a],l=(s?m:n)[a],p=_&&s?c(l,r):y&&"function"==typeof l?c(Function.call,l):l,m&&u(m,a,l,t&f.U),g[a]!=l&&o(g,a,p),y&&x[a]!=l&&(x[a]=l)};r.core=i,f.F=1,f.G=2,f.S=4,f.P=8,f.B=16,f.W=32,f.U=64,f.R=128,t.exports=f},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){var r=n(10),i=n(1).document,o=r(i)&&r(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,e,n){var r=n(1),i=n(3),o=n(7),u=n(12)("src"),c=Function.toString,f=(""+c).split("toString");n(2).inspectSource=function(t){return c.call(t)},(t.exports=function(t,e,n,c){var a="function"==typeof n;a&&(o(n,"name")||i(n,"name",e)),t[e]!==n&&(a&&(o(n,u)||i(n,u,t[e]?""+t[e]:f.join(String(e)))),t===r?t[e]=n:c?t[e]?t[e]=n:i(t,e,n):(delete t[e],i(t,e,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||c.call(this)})},function(t,e,n){var r=n(36);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,i){return t.call(e,n,r,i)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(42),i=n(9);t.exports=function(t){return r(i(t))}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e,n){var r=n(8),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,e,n){var r=n(2),i=n(1),o=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(t.exports=function(t,e){return o[t]||(o[t]=void 0!==e?e:{})})("versions",[]).push({version:r.version,mode:n(15)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,n){var r=n(4).f,i=n(7),o=n(0)("toStringTag");t.exports=function(t,e,n){t&&!i(t=n?t:t.prototype,o)&&r(t,o,{configurable:!0,value:e})}},function(t,e,n){var r=n(9);t.exports=function(t){return Object(r(t))}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=Math.PI/180;e.default=function(t){return t*r}},function(t,e,n){"use strict";n(30);var r=n(54),i=function(t){return t&&t.__esModule?t:{default:t}}(r);t.exports=i.default},function(t,e,n){n(31),n(47),t.exports=n(2).Array.from},function(t,e,n){"use strict";var r=n(32)(!0);n(33)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=r(e,n),this._i+=t.length,{value:t,done:!1})})},function(t,e,n){var r=n(8),i=n(9);t.exports=function(t){return function(e,n){var o,u,c=String(i(e)),f=r(n),a=c.length;return f<0||f>=a?t?"":void 0:(o=c.charCodeAt(f),o<55296||o>56319||f+1===a||(u=c.charCodeAt(f+1))<56320||u>57343?t?c.charAt(f):o:t?c.slice(f,f+2):u-56320+(o-55296<<10)+65536)}}},function(t,e,n){"use strict";var r=n(15),i=n(16),o=n(19),u=n(3),c=n(13),f=n(37),a=n(26),s=n(46),l=n(0)("iterator"),p=!([].keys&&"next"in[].keys()),h=function(){return this};t.exports=function(t,e,n,d,v,y,_){f(n,e,d);var m,g,x,b=function(t){if(!p&&t in M)return M[t];switch(t){case"keys":case"values":return function(){return new n(this,t)}}return function(){return new n(this,t)}},O=e+" Iterator",w="values"==v,j=!1,M=t.prototype,S=M[l]||M["@@iterator"]||v&&M[v],P=S||b(v),A=v?w?b("entries"):P:void 0,T="Array"==e?M.entries||S:S;if(T&&(x=s(T.call(new t)))!==Object.prototype&&x.next&&(a(x,O,!0),r||"function"==typeof x[l]||u(x,l,h)),w&&S&&"values"!==S.name&&(j=!0,P=function(){return S.call(this)}),r&&!_||!p&&!j&&M[l]||u(M,l,P),c[e]=P,c[O]=h,v)if(m={values:w?P:b("values"),keys:y?P:b("keys"),entries:A},_)for(g in m)g in M||o(M,g,m[g]);else i(i.P+i.F*(p||j),e,m);return m}},function(t,e,n){t.exports=!n(6)&&!n(17)(function(){return 7!=Object.defineProperty(n(18)("div"),"a",{get:function(){return 7}}).a})},function(t,e,n){var r=n(10);t.exports=function(t,e){if(!r(t))return t;var n,i;if(e&&"function"==typeof(n=t.toString)&&!r(i=n.call(t)))return i;if("function"==typeof(n=t.valueOf)&&!r(i=n.call(t)))return i;if(!e&&"function"==typeof(n=t.toString)&&!r(i=n.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,n){"use strict";var r=n(38),i=n(11),o=n(26),u={};n(3)(u,n(0)("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=r(u,{next:i(1,n)}),o(t,e+" Iterator")}},function(t,e,n){var r=n(5),i=n(39),o=n(25),u=n(14)("IE_PROTO"),c=function(){},f=function(){var t,e=n(18)("iframe"),r=o.length;for(e.style.display="none",n(45).appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(" + +
@@ -31,175 +33,75 @@

Features

Download on GitHub

- - - -

Demos

Basic Arc

Here’s some curved text that flows clockwise.

Here’s some curved text flowing clockwise.

- <h2 id="demo1">Here’s some curved text flowing clockwise.</h2> -new CircleType(document.getElementById('demo1')).radius(384); - - - - - +
// <h2 id="demo1">Here’s some curved text flowing clockwise.</h2>
+new CircleType(document.getElementById('demo1'))
+  .radius(384);

Reversed Arc

By setting dir to -1, the text will flow counter-clockwise instead.

Here’s some curved text flowing counter-clockwise.

- <h2 id="demo2">Here’s some curved text flowing counter-clockwise.</h2> -new CircleType(document.getElementById('demo2')).dir(-1).radius(384); - - - - - +
// <h2 id="demo2">Here’s some curved text flowing counter-clockwise.</h2>
+new CircleType(document.getElementById('demo2'))
+  .dir(-1)
+  .radius(384);

Auto Radius

By leaving the radius empty, CircleType.js will find the perfect radius so the text makes a complete rotation.

This text makes a complete rotation no matter how long it is.

- <h2 id="demo3">This text makes a complete rotation no matter how long it is. </h2> -new CircleType(document.getElementById('demo3')); - - - - - +
// <h2 id="demo3">This text makes a complete rotation no matter how long it is. </h2>
+new CircleType(document.getElementById('demo3'));

Fluid

Update the radius when the window is resized to create a fluid effect (try resizing your window).

This curved type shrinks and expands to fit inside its container.

- <h2 id="demo4">This curved type shrinks and expands to fit inside its container. </h2> +
// <h2 id="demo4">This curved type shrinks and expands to fit inside its container. </h2>
 var demo4 = new CircleType(document.getElementById('demo4'));
 window.addEventListener('resize', function updateRadius() {
   demo4.radius(demo4.element.offsetWidth / 2);
 });
-updateRadius();
-
-      
-      
-      
-
+updateRadius();

Using FitText.js

-

Here’s how you can use FitText.js to make the text scale (try resizing your window)

+

Here’s how you can use FitText.js to make the text scale (try resizing your window).

I play well with FitText.js too!

- <h2 id="demo5">I play well with FitText.js too! </h2> -var demo5 = new CircleType(document.getElementById('demo5')).radius(180); -$(demo5.element).fitText(); - - - - - +
// <h2 id="demo5">I play well with FitText.js too! </h2>
+var demo5 = new CircleType(document.getElementById('demo5'))
+  .radius(180);
+$(demo5.element).fitText();

Destroy

Here’s how you can remove the effect from an element.

Destroy Me

Easily remove the effect.

- <button id="destroyButton">Destroy Me</button> -<h2 id="demo6">Easily remove the effect.</h2> -var demo6 = new CircleType(document.getElementById('demo6')).radius(180); -document.getElementById('destroyButton').addEventListener('click', demo6.destroy.bind(demo6)); - - - - - +
// <button id="destroyButton">Destroy Me</button>
+// <h2 id="demo6">Easily remove the effect.</h2>
+var demo6 = new CircleType(document.getElementById('demo6'))
+  .radius(180);
+document.getElementById('destroyButton')
+  .addEventListener('click', demo6.destroy.bind(demo6));

Emojis

-

I work with emojis!

+

I work with emojis but you’ll need to provide your own splitter function. Here is an example that uses GraphemeSplitter:

-

🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩

+

👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦

- <h2 id="demo7">🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩</h2> -var demo7 = new CircleType(document.getElementById('demo7')); - - - - - +
// <script src="https://cdn.rawgit.com/orling/grapheme-splitter/b4500feb/index.js"></script>
+// <h2 id="demo7">👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦👨‍👩‍👧‍👦</h2>
+var splitter = new GraphemeSplitter()
+var demo7 = new CircleType(
+  document.getElementById('demo7'),
+  splitter.splitGraphemes.bind(splitter)
+);

Browser Support

+ + diff --git a/package.json b/package.json index d2f47ae..fdc6dee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "circletype", - "version": "2.2.1", + "version": "2.3.0", "description": "A JavaScript library that lets you curve type on the web.", "main": "dist/circletype.min.js", "files": [ diff --git a/src/class.js b/src/class.js index 518d702..21d3a8b 100644 --- a/src/class.js +++ b/src/class.js @@ -10,6 +10,8 @@ const { PI, max, min } = Math; * A CircleType instance creates a circular text element. * * @param {HTMLElement} elem A target HTML element. + * @param {Function} [splitter] An optional function used to split the element's + * text content into individual characters * * @example * // Instantiate `CircleType` with an HTML element. @@ -17,19 +19,30 @@ const { PI, max, min } = Math; * * // Set the text radius and direction. Note: setter methods are chainable. * circleType.radius(200).dir(-1); + * + * // Provide your own splitter function to handle emojis + * // @see https://github.com/orling/grapheme-splitter + * const splitter = new GraphemeSplitter() + * new CircleType( + * document.getElementById('myElement'), + * splitter.splitGraphemes.bind(splitter) + * ); + * */ class CircleType { - constructor(elem) { + constructor(elem, splitter) { this.element = elem; this.originalHTML = this.element.innerHTML; const container = document.createElement('div'); + const fragment = document.createDocumentFragment(); container.setAttribute('aria-label', elem.innerText); container.style.position = 'relative'; this.container = container; - this._letters = splitNode(elem); - this._letters.forEach(letter => container.appendChild(letter)); + this._letters = splitNode(elem, splitter); + this._letters.forEach(letter => fragment.appendChild(letter)); + container.appendChild(fragment); this.element.innerHTML = ''; this.element.appendChild(container); diff --git a/src/utils/__tests__/splitNode-test.js b/src/utils/__tests__/splitNode-test.js index 12993ad..627743c 100644 --- a/src/utils/__tests__/splitNode-test.js +++ b/src/utils/__tests__/splitNode-test.js @@ -45,15 +45,6 @@ describe('splitNode', () => { }); }); - it('handles any node', () => { - const testText = 'Some test text.'; - const { length } = testText; - const nodeTypes = [ 'div', 'a', 'time', 'asdf' ]; - const nodes = nodeTypes.map(type => createNode(testText, type)); - - nodes.forEach(node => expect(splitNode(node)).toHaveLength(length)); - }); - it('handles nodes with no text content', () => { expect(splitNode(createNode(''))).toHaveLength(0); }); @@ -62,18 +53,12 @@ describe('splitNode', () => { expect(splitNode(createNode(' '))).toHaveLength(0); }); - it('allows chars to be wrapped by any tag', () => { - const testText = 'X'; - const node = createNode(testText); - const [ anchor ] = splitNode(node, 'a'); - const [ div ] = splitNode(node, 'div'); - const [ button ] = splitNode(node, 'button'); - const [ unknown ] = splitNode(node, 'asdf'); - - expect(anchor).toBeInstanceOf(HTMLAnchorElement); - expect(div).toBeInstanceOf(HTMLDivElement); - expect(button).toBeInstanceOf(HTMLButtonElement); - expect(unknown).toBeInstanceOf(HTMLUnknownElement); + it('accepts a custom splitter function', () => { + const spans = splitNode( + createNode('one-two-three-four'), + string => string.split('-'), + ); + expect(spans).toHaveLength(4); }); it('handles all emojis (chars whose length might be `2`)', () => { diff --git a/src/utils/splitNode.js b/src/utils/splitNode.js index 59ec79a..c0bb67d 100644 --- a/src/utils/splitNode.js +++ b/src/utils/splitNode.js @@ -1,16 +1,19 @@ /** - * Splits the text of the provided element into its individual chars, wrapping - * each with an instance of the provided wrapper element. + * Splits the text of the provided element into its individual characters, + * wrapping each in an `HTMLSpanElement`. * * @param {Node} node The node whose `innerText` will be split. - * @param {string} [wrapper='span'] The name of the element to wrap each char. + * @param {Function} splitter An optional function used to split the node's text + * content into individual characters * * @return {Element[]} The wrapped split chars. */ -export default (node, wrapper = 'span') => { - const wrapperElement = document.createElement(wrapper); +export default (node, splitter) => { + const wrapperElement = document.createElement('span'); + const text = node.innerText.trim(); + const chars = splitter ? splitter(text) : [ ...text ]; - return [ ...node.innerText.trim() ].map(char => { + return chars.map(char => { const parent = wrapperElement.cloneNode(); parent.insertAdjacentHTML('afterbegin', char === ' ' ? ' ' : char);