├── .gitignore ├── LICENSE ├── README.md ├── images ├── fav.png └── social.png ├── index.html ├── package-lock.json ├── package.json ├── rough-notation.iife.js └── script.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Preet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rough-notation-web 2 | roughnotation.com website 3 | -------------------------------------------------------------------------------- /images/fav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rough-stuff/rough-notation-web/b8f40eca3be5504f555238a7777e73c0d5eaaaa7/images/fav.png -------------------------------------------------------------------------------- /images/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rough-stuff/rough-notation-web/b8f40eca3be5504f555238a7777e73c0d5eaaaa7/images/social.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RoughNotation 8 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 |

Rough Notation

115 |

A small JavaScript library to create and animate annotations on a web page

116 |

117 | Rough Notation uses RoughJS to create a 118 | hand-drawn look and feel. 119 | Elements can be annotated in a number of different styles. Animation duration and delay can be configured, or 120 | just turned off. 121 |

122 |

Rough Notation is 3.8kb in size when gzipped, and the code is available on 123 | GitHub.

124 |

125 | 126 | 127 | 128 |

129 |

130 | 131 | 132 | 133 |

134 |

 

135 |

Following are the different styles of annotations. Hit the annotate button in each section 136 | to see the animated annotation

137 |
138 |
139 |
140 |
141 |

Underline

142 |

Create a sketchy underline below an element.

143 |

144 | 145 |

146 |
147 |
148 |
149 |
150 |

Box

151 |

This style draws a box around the element.

152 |

153 | 154 |

155 |
156 |
157 |
158 |
159 |

Circle

160 |

Draw a circle around the element.

161 |

162 | 163 |

164 |
165 |
166 |
167 |
168 |

Highlight

169 |

Creates a highlight effect as if marked by a highlighter.

170 |

171 | 172 |

173 |
174 |
175 |
176 |
177 |

Strike-Through

178 |

Draw a hand-drawn line through an element creating a stroke-through effect.

179 |

180 | 181 |

182 |
183 |
184 |
185 |
186 |

Crossed-Off

187 |

To symbolize rejection, use a crossed-off effect on an element.

188 |

189 | 190 |

191 |
192 |
193 |
194 |
195 |

Brackets

196 |

Create a hand-drawn bracket around a block (like a paragraph of text) on one or multiple sides of the block. 197 |

198 |

199 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed accumsan nisi hendrerit augue molestie tempus. 200 | Phasellus purus quam, aliquet nec commodo quis, pharetra ut orci. Donec laoreet ligula nisl, 201 | placerat molestie mauris luctus id. Fusce dapibus non libero nec lobortis. Nullam iaculis nisl ac eros 202 | consequat, sit amet placerat massa vulputate. 203 |

204 |

205 | 206 |

207 |
208 |
209 |
210 |
211 |

Multiple lines

212 |

Ability to annotate inline content that can span multiple lines

213 |

214 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed accumsan nisi hendrerit augue molestie tempus. 215 | Phasellus purus quam, aliquet nec commodo quis, pharetra ut orci. Donec laoreet ligula nisl, 216 | placerat molestie mauris luctus id. Fusce dapibus non libero nec lobortis. Nullam iaculis nisl ac eros 217 | consequat, sit amet placerat 218 | massa vulputate. Maecenas euismod volutpat ultrices. Pellentesque felis ex, ullamcorper in felis finibus, 219 | feugiat 220 | dignissim augue. Integer malesuada non eros consectetur interdum. Mauris mollis non urna in porta. 221 |

222 |

223 | 224 |

225 |
226 |
227 |
228 |
229 |

Annotation Group

230 |

Rough Notation provides a way to order the animation of annotations by creating an 231 | annotation-group. Pass the list of annotations to create a group. 232 | When show is called on the group, the annotations are animated in order.

233 |

234 | 235 |

236 |
237 |
238 |
239 |
240 |

Annotation Styling

241 |

Various properties of the annotation can be configured, like color, strokeWidth, animation 242 | duration. 243 |

244 |

245 | 246 |

247 |
248 |
249 |
250 |
251 |

No Animation

252 |

Of course you don't have to animate the annotation, it just shows up when show is called.

253 |

254 | 255 |

256 |
257 |
258 |
259 |
260 |

 

261 |

262 | All the code and documentation is available on Github. 263 |

264 |

265 | 266 | 267 | 268 |

269 |

270 | 271 | 272 | 273 |

274 |

275 | Reach out on twitter @preetster 277 |

278 |
279 |
280 |
281 | 282 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rough-notation-web", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "rough-notation": { 8 | "version": "0.4.0", 9 | "resolved": "https://registry.npmjs.org/rough-notation/-/rough-notation-0.4.0.tgz", 10 | "integrity": "sha512-hDgn9qMIxWnfY+WWjt30aLMtDHR13H8r/NI3EQNdJiEFAzpklH5nKg4eH/thpxCWTYzyg8ueMsKIfz9MzOBQ4Q==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rough-notation-web", 3 | "version": "1.0.0", 4 | "description": "roughnotation.com website", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/pshihn/rough-notation-web.git" 12 | }, 13 | "author": "Preet Shihn", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/pshihn/rough-notation-web/issues" 17 | }, 18 | "homepage": "https://github.com/pshihn/rough-notation-web#readme", 19 | "dependencies": { 20 | "rough-notation": "^0.4.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rough-notation.iife.js: -------------------------------------------------------------------------------- 1 | var RoughNotation=function(t){"use strict";const e="http://www.w3.org/2000/svg";class s{constructor(t){this.seed=t}next(){return this.seed?(2**31-1&(this.seed=Math.imul(48271,this.seed)))/2**31:Math.random()}}function i(t,e,s,i,n){return{type:"path",ops:u(t,e,s,i,n)}}function n(t,e,s){const n=(t||[]).length;if(n>2){const i=[];for(let e=0;e500?.4:-.0016668*u+1.233334;let l=n.maxRandomnessOffset||0;l*l*100>a&&(l=u/10);const g=l/2,d=.2+.2*h(n);let p=n.bowing*n.maxRandomnessOffset*(i-e)/200,_=n.bowing*n.maxRandomnessOffset*(t-s)/200;p=c(p,n,f),_=c(_,n,f);const m=[],w=()=>c(g,n,f),v=()=>c(l,n,f);return o&&(r?m.push({op:"move",data:[t+w(),e+w()]}):m.push({op:"move",data:[t+c(l,n,f),e+c(l,n,f)]})),r?m.push({op:"bcurveTo",data:[p+t+(s-t)*d+w(),_+e+(i-e)*d+w(),p+t+2*(s-t)*d+w(),_+e+2*(i-e)*d+w(),s+w(),i+w()]}):m.push({op:"bcurveTo",data:[p+t+(s-t)*d+v(),_+e+(i-e)*d+v(),p+t+2*(s-t)*d+v(),_+e+2*(i-e)*d+v(),s+v(),i+v()]}),m}function l(t,e,s){const i=t.length,n=[];if(i>3){const o=[],r=1-s.curveTightness;n.push({op:"move",data:[t[1][0],t[1][1]]});for(let e=1;e+2t.setAttribute(e,s);for(const a of s){const s=document.createElementNS(e,"path");if(r(s,"d",a),r(s,"fill","none"),r(s,"stroke",h.color||"currentColor"),r(s,"stroke-width",""+l),p){const t=s.getTotalLength();i.push(t),o+=t}t.appendChild(s),n.push(s)}if(p){let t=0;for(let e=0;e{this._resizing||(this._resizing=!0,setTimeout(()=>{this._resizing=!1,"showing"===this._state&&this.haveRectsChanged()&&this.show()},400))},this._e=t,this._config=JSON.parse(JSON.stringify(e)),this.attach()}get animate(){return this._config.animate}set animate(t){this._config.animate=t}get animationDuration(){return this._config.animationDuration}set animationDuration(t){this._config.animationDuration=t}get iterations(){return this._config.iterations}set iterations(t){this._config.iterations=t}get color(){return this._config.color}set color(t){this._config.color!==t&&(this._config.color=t,this.refresh())}get strokeWidth(){return this._config.strokeWidth}set strokeWidth(t){this._config.strokeWidth!==t&&(this._config.strokeWidth=t,this.refresh())}get padding(){return this._config.padding}set padding(t){this._config.padding!==t&&(this._config.padding=t,this.refresh())}attach(){if("unattached"===this._state&&this._e.parentElement){!function(){if(!window.__rno_kf_s){const t=window.__rno_kf_s=document.createElement("style");t.textContent="@keyframes rough-notation-dash { to { stroke-dashoffset: 0; } }",document.head.appendChild(t)}}();const t=this._svg=document.createElementNS(e,"svg");t.setAttribute("class","rough-annotation");const s=t.style;s.position="absolute",s.top="0",s.left="0",s.overflow="visible",s.pointerEvents="none",s.width="100px",s.height="100px";const i="highlight"===this._config.type;if(this._e.insertAdjacentElement(i?"beforebegin":"afterend",t),this._state="not-showing",i){const t=window.getComputedStyle(this._e).position;(!t||"static"===t)&&(this._e.style.position="relative")}this.attachListeners()}}detachListeners(){window.removeEventListener("resize",this._resizeListener),this._ro&&this._ro.unobserve(this._e)}attachListeners(){this.detachListeners(),window.addEventListener("resize",this._resizeListener,{passive:!0}),!this._ro&&"ResizeObserver"in window&&(this._ro=new window.ResizeObserver(t=>{for(const e of t)e.contentRect&&this._resizeListener()})),this._ro&&this._ro.observe(this._e)}haveRectsChanged(){if(this._lastSizes.length){const t=this.rects();if(t.length!==this._lastSizes.length)return!0;for(let e=0;eMath.round(t)===Math.round(e);return s(t.x,e.x)&&s(t.y,e.y)&&s(t.w,e.w)&&s(t.h,e.h)}isShowing(){return"not-showing"!==this._state}refresh(){this.isShowing()&&!this.pendingRefresh&&(this.pendingRefresh=Promise.resolve().then(()=>{this.isShowing()&&this.show(),delete this.pendingRefresh}))}show(){switch(this._state){case"unattached":break;case"showing":this.hide(),this._svg&&this.render(this._svg,!0);break;case"not-showing":this.attach(),this._svg&&this.render(this._svg,!1)}}hide(){if(this._svg)for(;this._svg.lastChild;)this._svg.removeChild(this._svg.lastChild);this._state="not-showing"}remove(){this._svg&&this._svg.parentElement&&this._svg.parentElement.removeChild(this._svg),this._svg=void 0,this._state="unattached",this.detachListeners()}render(t,e){let s=this._config;e&&(s=JSON.parse(JSON.stringify(this._config)),s.animate=!1);const i=this.rects();let n=0;i.forEach(t=>n+=t.w);const o=s.animationDuration||800;let r=0;for(let e=0;e document.querySelector(t); 5 | 6 | // export interface RoughAnnotationConfig { 7 | // type: RoughAnnotationType; 8 | // animate?: boolean; // defaults to true 9 | // animationDuration?: number; // defaulst to 1000ms 10 | // animationDelay?: number; // default = 0 11 | // color?: string; // defaults to currentColor 12 | // strokeWidth?: number; // default based on type 13 | // padding?: number; // defaults to 5px 14 | // } 15 | 16 | { 17 | // top 18 | const a1 = annotate($('h1'), { type: 'highlight', color: '#FFF176' }); 19 | const a2 = annotate($('header span.abox'), { type: 'box', color: '#F44336', padding: 3 }); 20 | const a3 = annotate($('header a'), { type: 'underline', color: '#2196F3', padding: 3, strokeWidth: 3 }); 21 | const a4 = annotate($('header span.acircle'), { type: 'circle', color: '#F44336', padding: 5 }); 22 | const ag = annotationGroup([a1, a2, a3, a4]); 23 | ag.show(); 24 | } 25 | 26 | { 27 | const config = { type: 'underline', strokeWidth: 3, padding: 3, color: '#B71C1C' }; 28 | const a1 = annotate($('#underlineSection h3'), config); 29 | const a2 = annotate($('#underlineSection span'), config); 30 | $('#underlineSection button').addEventListener('click', () => { 31 | a1.hide(); 32 | a2.hide(); 33 | a1.show(); 34 | a2.show(); 35 | }); 36 | } 37 | 38 | { 39 | const config = { type: 'box', strokeWidth: 2, padding: 4, color: '#4A148C' }; 40 | const a1 = annotate($('#boxSection h3'), config); 41 | const a2 = annotate($('#boxSection span'), config); 42 | $('#boxSection button').addEventListener('click', () => { 43 | a1.hide(); 44 | a2.hide(); 45 | a1.show(); 46 | a2.show(); 47 | }); 48 | } 49 | 50 | { 51 | const config = { type: 'circle', padding: 6, color: '#0D47A1' }; 52 | const a1 = annotate($('#circleSection h3'), config); 53 | const a2 = annotate($('#circleSection span'), config); 54 | $('#circleSection button').addEventListener('click', () => { 55 | a1.hide(); 56 | a2.hide(); 57 | a1.show(); 58 | a2.show(); 59 | }); 60 | } 61 | 62 | { 63 | const config = { type: 'highlight', color: '#FFD54F' }; 64 | const a1 = annotate($('#highlightSection h3'), config); 65 | const a2 = annotate($('#highlightSection span'), config); 66 | $('#highlightSection button').addEventListener('click', () => { 67 | a1.hide(); 68 | a2.hide(); 69 | a1.show(); 70 | a2.show(); 71 | }); 72 | } 73 | 74 | { 75 | const config = { type: 'strike-through', color: '#1B5E20', strokeWidth: 2 }; 76 | const a1 = annotate($('#strikeSection h3'), config); 77 | const a2 = annotate($('#strikeSection span'), config); 78 | $('#strikeSection button').addEventListener('click', () => { 79 | a1.hide(); 80 | a2.hide(); 81 | a1.show(); 82 | a2.show(); 83 | }); 84 | } 85 | 86 | { 87 | const config = { type: 'crossed-off', color: '#F57F17', strokeWidth: 2 }; 88 | const a1 = annotate($('#crossSection h3'), config); 89 | const a2 = annotate($('#crossSection span'), config); 90 | $('#crossSection button').addEventListener('click', () => { 91 | a1.hide(); 92 | a2.hide(); 93 | a1.show(); 94 | a2.show(); 95 | }); 96 | } 97 | 98 | { 99 | const a1 = annotate($('#bracketSection h3'), { type: 'bracket', color: 'red', strokeWidth: 2, brackets: 'top' }); 100 | const a2 = annotate($('#bracketSection .blockp'), { type: 'bracket', color: 'red', strokeWidth: 2, padding: [2, 10], brackets: ['left', 'right'] }); 101 | const ag = annotationGroup([a2, a1]); 102 | $('#bracketSection button').addEventListener('click', () => { 103 | ag.hide(); 104 | ag.show(); 105 | }); 106 | } 107 | 108 | { 109 | const config = { type: 'highlight', color: '#FFD54F', animationDuration: 1500, multiline: true, iterations: 1 }; 110 | const a1 = annotate($('#multilineSection #mlspan'), config); 111 | $('#multilineSection button').addEventListener('click', () => { 112 | a1.hide(); 113 | a1.show(); 114 | }); 115 | } 116 | 117 | { 118 | const a1 = annotate($('#noanimSection h3'), { type: 'box', color: '#263238', animate: false }); 119 | const a2 = annotate($('#noanimSection i'), { type: 'underline', color: '#263238', strokeWidth: 4, animate: false }); 120 | $('#noanimSection button').addEventListener('click', () => { 121 | a1.hide(); 122 | a2.hide(); 123 | a1.show(); 124 | a2.show(); 125 | }); 126 | } 127 | 128 | { 129 | const a1 = annotate($('#configSection h3'), { type: 'box', color: '#D50000', strokeWidth: 10 }); 130 | const a2 = annotate($('#configSection span'), { type: 'box', color: '#33691E', animationDuration: 3000 }); 131 | $('#configSection button').addEventListener('click', () => { 132 | a1.hide(); 133 | a2.hide(); 134 | a1.show(); 135 | a2.show(); 136 | }); 137 | } 138 | 139 | { 140 | const a1 = annotate($('#groupSection h3'), { type: 'box', color: '#BF360C' }); 141 | const a2 = annotate($('#groupSection span'), { type: 'highlight', color: '#FFFF00' }); 142 | const a3 = annotate($('#groupSection i'), { type: 'underline', color: '#BF360C', animationDuration: 300 }); 143 | const ag = annotationGroup([a2, a3, a1]); 144 | $('#groupSection button').addEventListener('click', () => { 145 | ag.hide(); 146 | ag.show(); 147 | }); 148 | } 149 | 150 | 151 | 152 | 153 | })(); --------------------------------------------------------------------------------