├── .gitignore ├── reference ├── .DS_Store ├── text │ ├── aqua.png │ ├── chrome.png │ ├── mauve.png │ ├── purple.png │ ├── radial.png │ ├── slate.png │ ├── sunset.png │ ├── tilt.png │ ├── yellow.png │ ├── horizon.png │ ├── paperbag.png │ ├── redblue.png │ ├── graydient.png │ ├── marbleslab.png │ ├── superhero.png │ └── italicoutline.png └── screenshots │ ├── aqua.png │ ├── tilt.png │ ├── .DS_Store │ ├── chrome.png │ ├── create.png │ ├── purple.png │ ├── radial.png │ ├── sunset.png │ ├── yellow.png │ ├── gallery.png │ ├── graydient.png │ ├── horizon.png │ └── superhero.png ├── less └── textures │ ├── Texture-Cork.png │ ├── Texture-Oak.png │ ├── Texture-Sand.png │ ├── Texture-Canvas.png │ ├── Texture-Denim.png │ ├── Texture-Walnut.png │ ├── Texture-Bouquet.png │ ├── Texture-Granite.png │ ├── Texture-Newsprint.png │ ├── Texture-PaperBag.png │ ├── Texture-Papyrus.png │ ├── Texture-Parchment.png │ ├── Texture-WovenMat.png │ ├── Texture-BrownMarble.png │ ├── Texture-FishFossil.png │ ├── Texture-GreenMarble.png │ ├── Texture-MediumWood.png │ ├── Texture-PurpleMesh.png │ ├── Texture-Stationery.png │ ├── Texture-WhiteMarble.png │ ├── Texture-RecycledPaper.png │ ├── Texture-WaterDroplets.png │ ├── Texture-BlueTissuePaper.png │ └── Texture-PinkTissuePaper.png ├── css └── fonts │ ├── MicrosoftSansSerif.eot │ ├── MicrosoftSansSerif.ttf │ └── MicrosoftSansSerif.woff ├── package.json ├── gulpfile.js ├── README.md ├── index.html └── js ├── wordart.js └── object-observe-lite.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /reference/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/.DS_Store -------------------------------------------------------------------------------- /reference/text/aqua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/aqua.png -------------------------------------------------------------------------------- /reference/text/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/chrome.png -------------------------------------------------------------------------------- /reference/text/mauve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/mauve.png -------------------------------------------------------------------------------- /reference/text/purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/purple.png -------------------------------------------------------------------------------- /reference/text/radial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/radial.png -------------------------------------------------------------------------------- /reference/text/slate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/slate.png -------------------------------------------------------------------------------- /reference/text/sunset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/sunset.png -------------------------------------------------------------------------------- /reference/text/tilt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/tilt.png -------------------------------------------------------------------------------- /reference/text/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/yellow.png -------------------------------------------------------------------------------- /reference/text/horizon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/horizon.png -------------------------------------------------------------------------------- /reference/text/paperbag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/paperbag.png -------------------------------------------------------------------------------- /reference/text/redblue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/redblue.png -------------------------------------------------------------------------------- /less/textures/Texture-Cork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Cork.png -------------------------------------------------------------------------------- /less/textures/Texture-Oak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Oak.png -------------------------------------------------------------------------------- /less/textures/Texture-Sand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Sand.png -------------------------------------------------------------------------------- /reference/screenshots/aqua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/aqua.png -------------------------------------------------------------------------------- /reference/screenshots/tilt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/tilt.png -------------------------------------------------------------------------------- /reference/text/graydient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/graydient.png -------------------------------------------------------------------------------- /reference/text/marbleslab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/marbleslab.png -------------------------------------------------------------------------------- /reference/text/superhero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/superhero.png -------------------------------------------------------------------------------- /css/fonts/MicrosoftSansSerif.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/css/fonts/MicrosoftSansSerif.eot -------------------------------------------------------------------------------- /css/fonts/MicrosoftSansSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/css/fonts/MicrosoftSansSerif.ttf -------------------------------------------------------------------------------- /less/textures/Texture-Canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Canvas.png -------------------------------------------------------------------------------- /less/textures/Texture-Denim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Denim.png -------------------------------------------------------------------------------- /less/textures/Texture-Walnut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Walnut.png -------------------------------------------------------------------------------- /reference/screenshots/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/.DS_Store -------------------------------------------------------------------------------- /reference/screenshots/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/chrome.png -------------------------------------------------------------------------------- /reference/screenshots/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/create.png -------------------------------------------------------------------------------- /reference/screenshots/purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/purple.png -------------------------------------------------------------------------------- /reference/screenshots/radial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/radial.png -------------------------------------------------------------------------------- /reference/screenshots/sunset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/sunset.png -------------------------------------------------------------------------------- /reference/screenshots/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/yellow.png -------------------------------------------------------------------------------- /reference/text/italicoutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/text/italicoutline.png -------------------------------------------------------------------------------- /css/fonts/MicrosoftSansSerif.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/css/fonts/MicrosoftSansSerif.woff -------------------------------------------------------------------------------- /less/textures/Texture-Bouquet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Bouquet.png -------------------------------------------------------------------------------- /less/textures/Texture-Granite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Granite.png -------------------------------------------------------------------------------- /less/textures/Texture-Newsprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Newsprint.png -------------------------------------------------------------------------------- /less/textures/Texture-PaperBag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-PaperBag.png -------------------------------------------------------------------------------- /less/textures/Texture-Papyrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Papyrus.png -------------------------------------------------------------------------------- /less/textures/Texture-Parchment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Parchment.png -------------------------------------------------------------------------------- /less/textures/Texture-WovenMat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-WovenMat.png -------------------------------------------------------------------------------- /reference/screenshots/gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/gallery.png -------------------------------------------------------------------------------- /reference/screenshots/graydient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/graydient.png -------------------------------------------------------------------------------- /reference/screenshots/horizon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/horizon.png -------------------------------------------------------------------------------- /reference/screenshots/superhero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/reference/screenshots/superhero.png -------------------------------------------------------------------------------- /less/textures/Texture-BrownMarble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-BrownMarble.png -------------------------------------------------------------------------------- /less/textures/Texture-FishFossil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-FishFossil.png -------------------------------------------------------------------------------- /less/textures/Texture-GreenMarble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-GreenMarble.png -------------------------------------------------------------------------------- /less/textures/Texture-MediumWood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-MediumWood.png -------------------------------------------------------------------------------- /less/textures/Texture-PurpleMesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-PurpleMesh.png -------------------------------------------------------------------------------- /less/textures/Texture-Stationery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-Stationery.png -------------------------------------------------------------------------------- /less/textures/Texture-WhiteMarble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-WhiteMarble.png -------------------------------------------------------------------------------- /less/textures/Texture-RecycledPaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-RecycledPaper.png -------------------------------------------------------------------------------- /less/textures/Texture-WaterDroplets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-WaterDroplets.png -------------------------------------------------------------------------------- /less/textures/Texture-BlueTissuePaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-BlueTissuePaper.png -------------------------------------------------------------------------------- /less/textures/Texture-PinkTissuePaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizzitano/css3wordart/HEAD/less/textures/Texture-PinkTissuePaper.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css3wordart", 3 | "version": "0.0.1", 4 | "description": "wordart for modern times", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "build": "gulp less", 8 | "start": "gulp connect", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "ari rizzitano", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "gulp": "^3.8.8", 15 | "gulp-connect": "~2.0.5", 16 | "gulp-jshint": "~1.6.1", 17 | "gulp-less": "~1.2.3" 18 | }, 19 | "dependencies": { 20 | "object.observe": "^0.2.6" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | 4 | var jshint = require('gulp-jshint'); 5 | var less = require('gulp-less'); 6 | var connect = require('gulp-connect'); 7 | 8 | gulp.task('lint', function() { 9 | return gulp.src('./js/*.js') 10 | .pipe(jshint()) 11 | .pipe(jshint.reporter('default')); 12 | }); 13 | 14 | gulp.task('less', function () { 15 | gulp.src('./less/*.less') 16 | .pipe(less({ 17 | paths: [ path.join(__dirname, 'less', 'includes') ] 18 | })) 19 | .on('error', errorHandler) 20 | .pipe(gulp.dest('./css')); 21 | }); 22 | 23 | gulp.task('connect', function() { 24 | connect.server({ 25 | root: '.' 26 | }); 27 | }); 28 | 29 | var watcher = gulp.watch('./less/*.less', ['less'] ); 30 | 31 | watcher.on('change', function(event) { 32 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 33 | }); 34 | 35 | gulp.task('default', function() { ['lint', 'less'] }); 36 | 37 | function errorHandler (error) { 38 | console.log(error.toString()); 39 | this.emit('end'); 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSS3 WordArt 2 | ============ 3 | 4 | 1990s MS WordArt. In CSS3. 5 | 6 | View a demo at http://arizzitano.github.io/css3wordart. Note: it's not completely done yet. 7 | 8 | ### WHAT 9 | 10 | WordArt was a feature in older versions of Microsoft Office that generated stylized text. Its wide availability and user-friendliness led to broad use (and abuse) in signage, logos, and presentations. The unique presets are immediately recognizable typographic relics of the late 90s/early 00s. CSS3 WordArt allows users to generate and modify WordArt-like text styled with CSS3. 11 | 12 | CSS3 WordArt is an experimental project developed entirely in Chrome. Any working functionality in other browsers is purely coincidental. 13 | 14 | ### HOW 15 | 16 | I recreated as much of the original WordArt styles as possible using only CSS (LESS, to make things a little more DRY). I harvested source images (available for your perusal in /reference) from the actual WordArt creation interface in a copy of MS Word I ran in a Windows 95 VM. Both pieces of software came from completely legitimate sources, which is obviously why the screenshots are all in Italian. I used the original colors and background tiles, so it's as close as possible as you can get to real WordArt. 17 | 18 | **CSS** 19 | 20 | You might notice that my recreation isn't entirely true to the original. The one thing I couldn't quite reproduce (programmatically, at least) entirely with CSS were curved baselines and bounding boxes. Since CSS3 doesn't support bezier transforms on bounding box edges, I was not able to capture the arched, pinched, wavy, or swoosh shapes applied to some of the WordArt presets. I could probably have done it by applying individual classes to each letter and then applying specific transforms to each one, but that's janky, gross, and unsemantic, and besides, the point of the project was to just do it with CSS. 21 | 22 | CSS3 WordArt will only work in webkit browsers. The primary thing that allows it to work properly is `-webkit-background-clip: text;` which enables the gradient and image backgrounds directly on text. FF doesn't support this property, so it's pretty broken there. Without the gradient and image backgrounds, the styles look a whole lot less like actual WordArt, so I just didn't bother. Apparently there's a way to get around this using SVG, which I'll look into eventually. 23 | 24 | **JS** 25 | 26 | I reproduced most of the WordArt creation interface in addition to the styles, so you can get the full experience. It uses fairly minimal JS that I wrote from scratch. No libraries. I'd call it vanilla, but there's a fair amount of kink involved. I used Gulp as a build tool, then Gulp became un-trendy. C'est la vie. 27 | 28 | ### WHY 29 | 30 | WordArt is lovably tacky. Like [Comic Sans](http://www.comicsanscriminal.com/) or [Screen Beans](http://www.bitbetter.com/), unmodified WordArt presets are generally associated with technical and design ineptitude. What better way to exercise one's technical and design muscles than by paying tribute to this antithesis of modern trendiness? 31 | 32 | tl;dr: 90s fever, baby. [It's so uncool it's cool again.](http://www.shopjeen.com/products/windows-95-backpack) 33 | 34 | ### WHO 35 | 36 | My name is [Ari](http://twitter.com/arizzitano), I'm a [frontend engineer](http://arizzitano.com), and I'm currently looking for a job. [Get at me](mailto:arizzitano@gmail.com). 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CSS3 WordArt 5 | 6 | 7 | 8 | 9 | 10 |
11 | 16 |
17 | 18 |
19 |

Welcome

20 |

AWould you like to make some WordArt?

21 |
22 | 23 | 24 |
25 |
26 | 27 | 59 | 60 |
61 |

Edit WordArt Text

62 | 73 |
74 | 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 |
83 |
84 | 85 |
86 |

WordArt

87 |
88 |
89 | 90 |
91 |
92 |
93 |
94 |
95 | 112 |
113 | 114 | 115 | -------------------------------------------------------------------------------- /js/wordart.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var CLASSES = [ 3 | 'outline','up','arc','squeeze','inverted-arc','basic-stack', 4 | 'italic-outline','slate','mauve','graydient','red-blue','brown-stack', 5 | 'radial','purple','green-marble','rainbow','aqua','texture-stack', 6 | 'paper-bag','sunset','tilt','blues','yellow-dash','green-stack', 7 | 'chrome','marble-slab','gray-block','superhero','horizon','stack-3d' 8 | ]; 9 | var BG_SPEED = 3; 10 | 11 | function Gallery (p) { 12 | this.el = document.querySelector('.gallery'); 13 | this.parentObject = p; 14 | this.wordArtObj = null; 15 | this.selectedStyle = CLASSES[0]; 16 | } 17 | 18 | Gallery.prototype.render = function () { 19 | var self = this; 20 | var target = self.el.querySelector('#galleryThumbs'); 21 | var template = self.el.querySelector('#galleryTemplate'); 22 | var stacked = self.el.querySelector('#galleryStackedTemplate'); 23 | CLASSES.forEach(function (c, i) { 24 | var tmpl = ((i+1) % 6 === 0) ? stacked : template; 25 | var clone = tmpl.content.cloneNode(true); 26 | var li = clone.querySelector('li'); 27 | li.setAttribute('data-style', c); 28 | [].forEach.call(li.querySelectorAll('.wordart'), function (n) { 29 | n.className = n.className + ' ' + c; 30 | }); 31 | li.addEventListener('click', function (e) { 32 | self.selectStyle(c, this); 33 | }); 34 | target.appendChild(clone); 35 | }); 36 | }; 37 | 38 | Gallery.prototype.selectStyle = function (style, el) { 39 | this.selectedStyle = style; 40 | var currSelected = el.parentNode.querySelector('.selected'); 41 | if (currSelected != null) { 42 | currSelected.className = currSelected.className.replace('selected', ''); 43 | } 44 | el.className = el.className + ' selected'; 45 | }; 46 | 47 | Gallery.prototype.open = function (w) { 48 | this.selectedStyle = CLASSES[0]; 49 | this.wordArtObj = w; 50 | this.el.style.display = 'block'; 51 | }; 52 | 53 | Gallery.prototype.close = function () { 54 | this.el.style.display = 'none'; 55 | this.wordArtObj = null; 56 | }; 57 | 58 | Gallery.prototype.bindHandlers = function () { 59 | var self = this; 60 | self.el.querySelector('button.ok').addEventListener('click', function () { 61 | self.wordArtObj.setStyle(self.selectedStyle); 62 | self.close(); 63 | self.parentObject.launchEditor(); 64 | }); 65 | self.el.querySelector('button.cancel').addEventListener('click', function () { 66 | self.close(); 67 | }); 68 | }; 69 | 70 | Gallery.prototype.init = function () { 71 | this.render(); 72 | this.bindHandlers(); 73 | }; 74 | 75 | 76 | 77 | 78 | function Editor (p) { 79 | this.parentObject = p; 80 | this.wordArtObj = null; 81 | this.el = document.querySelector('.editor'); 82 | this.defaultTxt = 'Your Text Here'; 83 | } 84 | 85 | Editor.prototype.init = function () { 86 | this.bindHandlers(); 87 | }; 88 | 89 | Editor.prototype.open = function (w) { 90 | this.wordArtObj = w; 91 | this.el.querySelector('textarea').value = this.defaultTxt; 92 | this.el.querySelector('textarea').select(); 93 | this.el.style.display = 'block'; 94 | }; 95 | 96 | Editor.prototype.close = function () { 97 | this.el.style.display = 'none'; 98 | this.wordArtObj = null; 99 | }; 100 | 101 | Editor.prototype.bindHandlers = function () { 102 | var self = this; 103 | self.el.querySelector('button.ok').addEventListener('click', function () { 104 | var txt = self.el.querySelector('textarea').value.trim() || this.defaultText(); 105 | self.wordArtObj.setText(txt); 106 | self.close(); 107 | self.parentObject.displayWordArt(); 108 | }); 109 | self.el.querySelector('button.cancel').addEventListener('click', function () { 110 | self.close(); 111 | }); 112 | }; 113 | 114 | 115 | 116 | 117 | function Stage (p) { 118 | this.parentObject = p; 119 | this.el = document.querySelector('.stage'); 120 | this.scr = null; 121 | } 122 | 123 | Stage.prototype.init = function () { 124 | this.open(); 125 | this.bindHandlers(); 126 | }; 127 | 128 | Stage.prototype.open = function () { 129 | this.el.style.display = 'block'; 130 | this.scr = this.el.querySelector('.stage').getBoundingClientRect(); 131 | }; 132 | 133 | Stage.prototype.bindHandlers = function () { 134 | var self = this; 135 | self.el.querySelector('.create').addEventListener('click', function () { 136 | self.parentObject.makeNewWordArt(); 137 | }); 138 | }; 139 | 140 | 141 | 142 | 143 | function Background (p) { 144 | this.parentObject = p; 145 | this.el = document.querySelector('.background'); 146 | this.bcr = this.el.getBoundingClientRect(); 147 | } 148 | 149 | Background.prototype.init = function () { 150 | this.bindHandlers(); 151 | for (var i=0; i 0 && this.position.left < this.availSize('width')) { 221 | this.position.left = newLeft; 222 | } else { 223 | this.dir.x = -this.dir.x; 224 | this.position.left = this.position.left + (this.dir.x * this.speed.x); 225 | } 226 | var newTop = this.position.top + (this.dir.y * this.speed.y); 227 | if (this.position.top > 0 && this.position.top < this.availSize('height')) { 228 | this.position.top = newTop; 229 | } else { 230 | this.dir.y = -this.dir.y; 231 | this.position.top = this.position.top + (this.dir.y * this.speed.y); 232 | } 233 | this.startTimer(); 234 | }; 235 | 236 | BgWordArt.prototype.bindHandlers = function () { 237 | var self = this; 238 | Object.observe(self.position, function (changes) { 239 | changes.forEach(function (c) { 240 | self.el.style[c.name] = c.object[c.name] + 'px'; 241 | }); 242 | }); 243 | }; 244 | 245 | 246 | 247 | 248 | function WordArt (p, s) { 249 | this.verticalStyles = ['basic-stack','brown-stack','green-stack','texture-stack','stack-3d']; 250 | this.parentObject = p; 251 | this.el = document.querySelector('.stage'); 252 | this.stage = s; 253 | 254 | this.selectedStyle, this.txt; 255 | 256 | this.resizable = null; 257 | this.rcr = null; 258 | this.wordArtObj = null; 259 | this.wcr = null; 260 | this.handles = null; 261 | 262 | this.isDragging = false; 263 | this.isResizing = false; 264 | this.resizeHandle = null; 265 | 266 | this.position = { 267 | left: 0, 268 | top: 0, 269 | width: null, 270 | height: null 271 | }; 272 | 273 | this.rescale = { 274 | scale: { 275 | scaleX: 1, 276 | scaleY: 1 277 | }, 278 | size: { 279 | height: null, 280 | width: null 281 | } 282 | }; 283 | } 284 | 285 | WordArt.prototype.setStyle = function (style) { 286 | this.selectedStyle = style; 287 | }; 288 | 289 | WordArt.prototype.setText = function (txt) { 290 | this.txt = txt; 291 | }; 292 | 293 | WordArt.prototype.init = function () { 294 | this.genDeltas(); 295 | this.render(); 296 | this.bindHandlers(); 297 | }; 298 | 299 | WordArt.prototype.render = function () { 300 | var self = this; 301 | var tmpl = self.el.querySelector('#finalWordart'); 302 | var clone = tmpl.content.cloneNode(true); 303 | var wa = clone.querySelector('.wordart'); 304 | var span = wa.querySelector('span'); 305 | wa.className = wa.className + ' ' + self.selectedStyle; 306 | span.setAttribute('data-text', self.txt); 307 | span.innerHTML = self.txt; 308 | 309 | self.resizable = clone.querySelector('.resizable'); 310 | self.wordArtObj = self.resizable.querySelector('.wordart'); 311 | self.handles = self.resizable.querySelectorAll('.h'); 312 | 313 | self.el.querySelector('.stage').appendChild(clone); 314 | setTimeout(function () { 315 | self.initSize(); 316 | }, 5); 317 | }; 318 | 319 | WordArt.prototype.initSize = function () { 320 | this.wcr = this.wordArtObj.getBoundingClientRect(); 321 | this.rcr = this.resizable.getBoundingClientRect(); 322 | this.position.width = (this.wcr.left - this.rcr.left) + 323 | this.wcr.width + 2; 324 | this.position.height = this.wcr.height + 2; 325 | this.rescale.size.height = this.position.height; 326 | this.rescale.size.width = this.position.width; 327 | this.rcr = this.resizable.getBoundingClientRect(); 328 | }; 329 | 330 | WordArt.prototype.move = function (e) { 331 | var newY = parseFloat(this.position.top) + e.movementY; 332 | if (newY > -1 && (newY + this.rcr.height) < this.stage.scr.height) { 333 | this.position.top = newY; 334 | } 335 | var newX = parseFloat(this.position.left) + e.movementX; 336 | if (newX > -1 && (newX + this.rcr.width) < this.stage.scr.width) { 337 | this.position.left = newX; 338 | } 339 | }; 340 | 341 | WordArt.prototype.genDeltas = function () { 342 | var self = this; 343 | [{ 344 | sizeProp: 'width', 345 | posProp: 'left', 346 | axis: 'x', 347 | edgeClass: 'w' 348 | }, { 349 | sizeProp: 'height', 350 | posProp: 'top', 351 | axis: 'y', 352 | edgeClass: 'n' 353 | }].forEach(function (args) { 354 | self['scale' + args.axis] = function (change) { 355 | var self = this; 356 | var classes = self.resizeHandle.className; 357 | var delta = change; 358 | var posDelta = 0; 359 | if (classes.indexOf((args.axis + 'c')) > -1) { 360 | delta = 0; 361 | } else if (classes.indexOf(args.edgeClass) > -1) { 362 | posDelta = delta; 363 | delta = -delta; 364 | } 365 | var newSize = self.rescale.size[args.sizeProp] + delta; 366 | var newPos = self.position[args.posProp] + posDelta; 367 | if ((newPos + newSize) < self.stage.scr[args.sizeProp] && newPos > -1) { 368 | self.position[args.posProp] = newPos; 369 | self.rescale.size[args.sizeProp] = newSize; 370 | } 371 | }; 372 | }); 373 | }; 374 | 375 | WordArt.prototype.resize = function (e) { 376 | this.scalex(e.movementX); 377 | this.scaley(e.movementY); 378 | this.rcr = this.resizable.getBoundingClientRect(); 379 | }; 380 | 381 | WordArt.prototype.bindHandlers = function () { 382 | var self = this; 383 | 384 | // dragging 385 | self.wordArtObj.addEventListener('mousedown', function (e) { 386 | self.isDragging = true; 387 | }); 388 | document.addEventListener('mouseup', function (e) { 389 | self.isDragging = false; 390 | self.isResizing = false; 391 | self.resizeHandle = null; 392 | }); 393 | self.el.addEventListener('mousemove', function (e) { 394 | if (self.isDragging) { 395 | self.move(e); 396 | } else if (self.isResizing) { 397 | self.resize(e); 398 | } 399 | }); 400 | 401 | // resizing 402 | [].forEach.call(self.handles, function (el) { 403 | el.addEventListener('mousedown', function (e) { 404 | self.isResizing = true; 405 | self.resizeHandle = e.target; 406 | }); 407 | }); 408 | 409 | // data change handlers 410 | Object.observe(self.position, function (changes) { 411 | changes.forEach(function (c) { 412 | self.resizable.style[c.name] = c.object[c.name] + 'px'; 413 | }); 414 | }); 415 | Object.observe(self.rescale.size, function (changes) { 416 | changes.forEach(function (c) { 417 | self.rescale.scale.scaleY = c.object.height / self.position.height; 418 | self.rescale.scale.scaleX = c.object.width / self.position.width; 419 | }); 420 | }); 421 | Object.observe(self.rescale.scale, function (changes) { 422 | changes.forEach(function (c) { 423 | ['transform','-webkit-transform','-moz-transform', 424 | '-o-transform','-ms-transform'].forEach(function (prop) { 425 | self.resizable.style[prop] = 'scaleX(' + 426 | c.object.scaleX + ') ' + 427 | 'scaleY(' + c.object.scaleY + ')'; 428 | }); 429 | }); 430 | }); 431 | }; 432 | 433 | WordArt.prototype.close = function () { 434 | this.el.style.display = 'none'; 435 | }; 436 | 437 | 438 | 439 | 440 | function WordArtMaker () { 441 | this.background = new Background(this); 442 | this.gallery = new Gallery(this); 443 | this.editor = new Editor(this); 444 | this.stage = new Stage(this); 445 | 446 | this.wordArt = null; 447 | this.wordArts = []; 448 | 449 | this.background.init(this); 450 | this.bindHandlers(); 451 | } 452 | 453 | WordArtMaker.prototype.init = function () { 454 | this.gallery.init(this); 455 | this.editor.init(this); 456 | this.stage.init(this); 457 | }; 458 | 459 | WordArtMaker.prototype.makeNewWordArt = function () { 460 | var w = new WordArt(this, this.stage); 461 | this.wordArts.push(w); 462 | this.gallery.open(w); 463 | }; 464 | 465 | WordArtMaker.prototype.launchEditor = function () { 466 | this.editor.open(this.wordArts[this.wordArts.length - 1]); 467 | }; 468 | 469 | WordArtMaker.prototype.displayWordArt = function () { 470 | this.stage.open(); 471 | this.wordArts[this.wordArts.length - 1].init(); 472 | }; 473 | 474 | WordArtMaker.prototype.bindHandlers = function () { 475 | var self = this; 476 | document.querySelectorAll('.welcome button').forEach(button => button.addEventListener('click', function () { 477 | document.querySelector('.welcome').style.display = 'none'; 478 | self.init(); 479 | self.makeNewWordArt(); 480 | })); 481 | }; 482 | 483 | document.addEventListener('DOMContentLoaded', function () { 484 | var w = new WordArtMaker(); 485 | }); 486 | }()); 487 | -------------------------------------------------------------------------------- /js/object-observe-lite.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Object.observe "lite" polyfill - v0.2.4 3 | * by Massimo Artizzu (MaxArt2501) 4 | * 5 | * https://github.com/MaxArt2501/object-observe 6 | * 7 | * Licensed under the MIT License 8 | * See LICENSE for details 9 | */ 10 | 11 | // Some type definitions 12 | /** 13 | * This represents the data relative to an observed object 14 | * @typedef {Object} ObjectData 15 | * @property {Map} handlers 16 | * @property {String[]} properties 17 | * @property {*[]} values 18 | * @property {Notifier} notifier 19 | */ 20 | /** 21 | * Function definition of a handler 22 | * @callback Handler 23 | * @param {ChangeRecord[]} changes 24 | */ 25 | /** 26 | * This represents the data relative to an observed object and one of its 27 | * handlers 28 | * @typedef {Object} HandlerData 29 | * @property {Map} observed 30 | * @property {ChangeRecord[]} changeRecords 31 | */ 32 | /** 33 | * @typedef {Object} ObservedData 34 | * @property {String[]} acceptList 35 | * @property {ObjectData} data 36 | */ 37 | /** 38 | * Type definition for a change. Any other property can be added using 39 | * the notify() or performChange() methods of the notifier. 40 | * @typedef {Object} ChangeRecord 41 | * @property {String} type 42 | * @property {Object} object 43 | * @property {String} [name] 44 | * @property {*} [oldValue] 45 | * @property {Number} [index] 46 | */ 47 | /** 48 | * Type definition for a notifier (what Object.getNotifier returns) 49 | * @typedef {Object} Notifier 50 | * @property {Function} notify 51 | * @property {Function} performChange 52 | */ 53 | /** 54 | * Function called with Notifier.performChange. It may optionally return a 55 | * ChangeRecord that gets automatically notified, but `type` and `object` 56 | * properties are overridden. 57 | * @callback Performer 58 | * @returns {ChangeRecord|undefined} 59 | */ 60 | 61 | Object.observe || (function(O, A, root, _undefined) { 62 | "use strict"; 63 | 64 | /** 65 | * Relates observed objects and their data 66 | * @type {Map>} 72 | */ 73 | handlers, 74 | 75 | defaultAcceptList = [ "add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions" ]; 76 | 77 | // Functions for internal usage 78 | 79 | /** 80 | * Checks if the argument is an Array object. Polyfills Array.isArray. 81 | * @function isArray 82 | * @param {?*} object 83 | * @returns {Boolean} 84 | */ 85 | var isArray = A.isArray || (function(toString) { 86 | return function (object) { return toString.call(object) === "[object Array]"; }; 87 | })(O.prototype.toString), 88 | 89 | /** 90 | * Returns the index of an item in a collection, or -1 if not found. 91 | * Uses the generic Array.indexOf or Array.prototype.indexOf if available. 92 | * @function inArray 93 | * @param {Array} array 94 | * @param {*} pivot Item to look for 95 | * @param {Number} [start=0] Index to start from 96 | * @returns {Number} 97 | */ 98 | inArray = A.prototype.indexOf ? A.indexOf || function(array, pivot, start) { 99 | return A.prototype.indexOf.call(array, pivot, start); 100 | } : function(array, pivot, start) { 101 | for (var i = start || 0; i < array.length; i++) 102 | if (array[i] === pivot) 103 | return i; 104 | return -1; 105 | }, 106 | 107 | /** 108 | * Returns an instance of Map, or a Map-like object is Map is not 109 | * supported or doesn't support forEach() 110 | * @function createMap 111 | * @returns {Map} 112 | */ 113 | createMap = root.Map === _undefined || !Map.prototype.forEach ? function() { 114 | // Lightweight shim of Map. Lacks clear(), entries(), keys() and 115 | // values() (the last 3 not supported by IE11, so can't use them), 116 | // it doesn't handle the constructor's argument (like IE11) and of 117 | // course it doesn't support for...of. 118 | // Chrome 31-35 and Firefox 13-24 have a basic support of Map, but 119 | // they lack forEach(), so their native implementation is bad for 120 | // this polyfill. (Chrome 36+ supports Object.observe.) 121 | var keys = [], values = []; 122 | 123 | return { 124 | size: 0, 125 | has: function(key) { return inArray(keys, key) > -1; }, 126 | get: function(key) { return values[inArray(keys, key)]; }, 127 | set: function(key, value) { 128 | var i = inArray(keys, key); 129 | if (i === -1) { 130 | keys.push(key); 131 | values.push(value); 132 | this.size++; 133 | } else values[i] = value; 134 | }, 135 | "delete": function(key) { 136 | var i = inArray(keys, key); 137 | if (i > -1) { 138 | keys.splice(i, 1); 139 | values.splice(i, 1); 140 | this.size--; 141 | } 142 | }, 143 | forEach: function(callback/*, thisObj*/) { 144 | for (var i = 0; i < keys.length; i++) 145 | callback.call(arguments[1], values[i], keys[i], this); 146 | } 147 | }; 148 | } : function() { return new Map(); }, 149 | 150 | /** 151 | * Simple shim for Object.getOwnPropertyNames when is not available 152 | * Misses checks on object, don't use as a replacement of Object.keys/getOwnPropertyNames 153 | * @function getProps 154 | * @param {Object} object 155 | * @returns {String[]} 156 | */ 157 | getProps = O.getOwnPropertyNames ? (function() { 158 | var func = O.getOwnPropertyNames; 159 | try { 160 | arguments.callee; 161 | } catch (e) { 162 | // Strict mode is supported 163 | 164 | // In strict mode, we can't access to "arguments", "caller" and 165 | // "callee" properties of functions. Object.getOwnPropertyNames 166 | // returns [ "prototype", "length", "name" ] in Firefox; it returns 167 | // "caller" and "arguments" too in Chrome and in Internet 168 | // Explorer, so those values must be filtered. 169 | var avoid = (func(inArray).join(" ") + " ").replace(/prototype |length |name /g, "").slice(0, -1).split(" "); 170 | if (avoid.length) func = function(object) { 171 | var props = O.getOwnPropertyNames(object); 172 | if (typeof object === "function") 173 | for (var i = 0, j; i < avoid.length;) 174 | if ((j = inArray(props, avoid[i++])) > -1) 175 | props.splice(j, 1); 176 | 177 | return props; 178 | }; 179 | } 180 | return func; 181 | })() : function(object) { 182 | // Poor-mouth version with for...in (IE8-) 183 | var props = [], prop, hop; 184 | if ("hasOwnProperty" in object) { 185 | for (prop in object) 186 | if (object.hasOwnProperty(prop)) 187 | props.push(prop); 188 | } else { 189 | hop = O.hasOwnProperty; 190 | for (prop in object) 191 | if (hop.call(object, prop)) 192 | props.push(prop); 193 | } 194 | 195 | // Inserting a common non-enumerable property of arrays 196 | if (isArray(object)) 197 | props.push("length"); 198 | 199 | return props; 200 | }, 201 | 202 | /** 203 | * Sets up the next check and delivering iteration, using 204 | * requestAnimationFrame or a (close) polyfill. 205 | * @function nextFrame 206 | * @param {function} func 207 | * @returns {number} 208 | */ 209 | nextFrame = root.requestAnimationFrame || root.webkitRequestAnimationFrame || (function() { 210 | var initial = +new Date, 211 | last = initial; 212 | return function(func) { 213 | return setTimeout(function() { 214 | func((last = +new Date) - initial); 215 | }, 17); 216 | }; 217 | })(), 218 | 219 | /** 220 | * Sets up the observation of an object 221 | * @function doObserve 222 | * @param {Object} object 223 | * @param {Handler} handler 224 | * @param {String[]} [acceptList] 225 | */ 226 | doObserve = function(object, handler, acceptList) { 227 | var data = observed.get(object); 228 | 229 | if (data) { 230 | performPropertyChecks(data, object); 231 | setHandler(object, data, handler, acceptList); 232 | } else { 233 | data = createObjectData(object); 234 | setHandler(object, data, handler, acceptList); 235 | 236 | if (observed.size === 1) 237 | // Let the observation begin! 238 | nextFrame(runGlobalLoop); 239 | } 240 | }, 241 | 242 | /** 243 | * Creates the initial data for an observed object 244 | * @function createObjectData 245 | * @param {Object} object 246 | */ 247 | createObjectData = function(object, data) { 248 | var props = getProps(object), 249 | values = [], i = 0, 250 | data = { 251 | handlers: createMap(), 252 | properties: props, 253 | values: values, 254 | notifier: retrieveNotifier(object, data) 255 | }; 256 | 257 | while (i < props.length) 258 | values[i] = object[props[i++]]; 259 | 260 | observed.set(object, data); 261 | 262 | return data; 263 | }, 264 | 265 | /** 266 | * Performs basic property value change checks on an observed object 267 | * @function performPropertyChecks 268 | * @param {ObjectData} data 269 | * @param {Object} object 270 | * @param {String} [except] Doesn't deliver the changes to the 271 | * handlers that accept this type 272 | */ 273 | performPropertyChecks = function(data, object, except) { 274 | if (!data.handlers.size) return; 275 | 276 | var props, proplen, keys, 277 | values = data.values, 278 | i = 0, idx, 279 | key, value, ovalue; 280 | 281 | props = data.properties.slice(); 282 | proplen = props.length; 283 | keys = getProps(object); 284 | 285 | // Check for value additions/changes 286 | while (i < keys.length) { 287 | key = keys[i++]; 288 | idx = inArray(props, key); 289 | value = object[key]; 290 | 291 | if (idx === -1) { 292 | addChangeRecord(object, data, { 293 | name: key, 294 | type: "add", 295 | object: object 296 | }, except); 297 | data.properties.push(key); 298 | values.push(value); 299 | } else { 300 | ovalue = values[idx]; 301 | props[idx] = null; 302 | proplen--; 303 | if (ovalue === value ? ovalue === 0 && 1/ovalue !== 1/value 304 | : ovalue === ovalue || value === value) { 305 | addChangeRecord(object, data, { 306 | name: key, 307 | type: "update", 308 | object: object, 309 | oldValue: ovalue 310 | }, except); 311 | data.values[idx] = value; 312 | } 313 | } 314 | } 315 | 316 | // Checks if some property has been deleted 317 | for (i = props.length; proplen && i--;) 318 | if (props[i] !== null) { 319 | addChangeRecord(object, data, { 320 | name: props[i], 321 | type: "delete", 322 | object: object, 323 | oldValue: values[i] 324 | }, except); 325 | data.properties.splice(i, 1); 326 | data.values.splice(i, 1); 327 | proplen--; 328 | } 329 | }, 330 | 331 | /** 332 | * Sets up the main loop for object observation and change notification 333 | * It stops if no object is observed. 334 | * @function runGlobalLoop 335 | */ 336 | runGlobalLoop = function() { 337 | if (observed.size) { 338 | observed.forEach(performPropertyChecks); 339 | handlers.forEach(deliverHandlerRecords); 340 | nextFrame(runGlobalLoop); 341 | } 342 | }, 343 | 344 | /** 345 | * Deliver the change records relative to a certain handler, and resets 346 | * the record list. 347 | * @param {HandlerData} hdata 348 | * @param {Handler} handler 349 | */ 350 | deliverHandlerRecords = function(hdata, handler) { 351 | var records = hdata.changeRecords; 352 | if (records.length) { 353 | hdata.changeRecords = []; 354 | handler(records); 355 | } 356 | }, 357 | 358 | /** 359 | * Returns the notifier for an object - whether it's observed or not 360 | * @function retrieveNotifier 361 | * @param {Object} object 362 | * @param {ObjectData} [data] 363 | * @returns {Notifier} 364 | */ 365 | retrieveNotifier = function(object, data) { 366 | if (arguments.length < 2) 367 | data = observed.get(object); 368 | 369 | /** @type {Notifier} */ 370 | return data && data.notifier || { 371 | /** 372 | * @method notify 373 | * @see http://arv.github.io/ecmascript-object-observe/#notifierprototype._notify 374 | * @memberof Notifier 375 | * @param {ChangeRecord} changeRecord 376 | */ 377 | notify: function(changeRecord) { 378 | changeRecord.type; // Just to check the property is there... 379 | 380 | // If there's no data, the object has been unobserved 381 | var data = observed.get(object); 382 | if (data) { 383 | var recordCopy = { object: object }, prop; 384 | for (prop in changeRecord) 385 | if (prop !== "object") 386 | recordCopy[prop] = changeRecord[prop]; 387 | addChangeRecord(object, data, recordCopy); 388 | } 389 | }, 390 | 391 | /** 392 | * @method performChange 393 | * @see http://arv.github.io/ecmascript-object-observe/#notifierprototype_.performchange 394 | * @memberof Notifier 395 | * @param {String} changeType 396 | * @param {Performer} func The task performer 397 | * @param {*} [thisObj] Used to set `this` when calling func 398 | */ 399 | performChange: function(changeType, func/*, thisObj*/) { 400 | if (typeof changeType !== "string") 401 | throw new TypeError("Invalid non-string changeType"); 402 | 403 | if (typeof func !== "function") 404 | throw new TypeError("Cannot perform non-function"); 405 | 406 | // If there's no data, the object has been unobserved 407 | var data = observed.get(object), 408 | prop, changeRecord, 409 | thisObj = arguments[2], 410 | result = thisObj === _undefined ? func() : func.call(thisObj); 411 | 412 | data && performPropertyChecks(data, object, changeType); 413 | 414 | // If there's no data, the object has been unobserved 415 | if (data && result && typeof result === "object") { 416 | changeRecord = { object: object, type: changeType }; 417 | for (prop in result) 418 | if (prop !== "object" && prop !== "type") 419 | changeRecord[prop] = result[prop]; 420 | addChangeRecord(object, data, changeRecord); 421 | } 422 | } 423 | }; 424 | }, 425 | 426 | /** 427 | * Register (or redefines) an handler in the collection for a given 428 | * object and a given type accept list. 429 | * @function setHandler 430 | * @param {Object} object 431 | * @param {ObjectData} data 432 | * @param {Handler} handler 433 | * @param {String[]} acceptList 434 | */ 435 | setHandler = function(object, data, handler, acceptList) { 436 | var hdata = handlers.get(handler); 437 | if (!hdata) 438 | handlers.set(handler, hdata = { 439 | observed: createMap(), 440 | changeRecords: [] 441 | }); 442 | hdata.observed.set(object, { 443 | acceptList: acceptList.slice(), 444 | data: data 445 | }); 446 | data.handlers.set(handler, hdata); 447 | }, 448 | 449 | /** 450 | * Adds a change record in a given ObjectData 451 | * @function addChangeRecord 452 | * @param {Object} object 453 | * @param {ObjectData} data 454 | * @param {ChangeRecord} changeRecord 455 | * @param {String} [except] 456 | */ 457 | addChangeRecord = function(object, data, changeRecord, except) { 458 | data.handlers.forEach(function(hdata) { 459 | var acceptList = hdata.observed.get(object).acceptList; 460 | // If except is defined, Notifier.performChange has been 461 | // called, with except as the type. 462 | // All the handlers that accepts that type are skipped. 463 | if ((typeof except !== "string" 464 | || inArray(acceptList, except) === -1) 465 | && inArray(acceptList, changeRecord.type) > -1) 466 | hdata.changeRecords.push(changeRecord); 467 | }); 468 | }; 469 | 470 | observed = createMap(); 471 | handlers = createMap(); 472 | 473 | /** 474 | * @function Object.observe 475 | * @see http://arv.github.io/ecmascript-object-observe/#Object.observe 476 | * @param {Object} object 477 | * @param {Handler} handler 478 | * @param {String[]} [acceptList] 479 | * @throws {TypeError} 480 | * @returns {Object} The observed object 481 | */ 482 | O.observe = function observe(object, handler, acceptList) { 483 | if (!object || typeof object !== "object" && typeof object !== "function") 484 | throw new TypeError("Object.observe cannot observe non-object"); 485 | 486 | if (typeof handler !== "function") 487 | throw new TypeError("Object.observe cannot deliver to non-function"); 488 | 489 | if (O.isFrozen && O.isFrozen(handler)) 490 | throw new TypeError("Object.observe cannot deliver to a frozen function object"); 491 | 492 | if (acceptList === _undefined) 493 | acceptList = defaultAcceptList; 494 | else if (!acceptList || typeof acceptList !== "object") 495 | throw new TypeError("Third argument to Object.observe must be an array of strings."); 496 | 497 | doObserve(object, handler, acceptList); 498 | 499 | return object; 500 | }; 501 | 502 | /** 503 | * @function Object.unobserve 504 | * @see http://arv.github.io/ecmascript-object-observe/#Object.unobserve 505 | * @param {Object} object 506 | * @param {Handler} handler 507 | * @throws {TypeError} 508 | * @returns {Object} The given object 509 | */ 510 | O.unobserve = function unobserve(object, handler) { 511 | if (object === null || typeof object !== "object" && typeof object !== "function") 512 | throw new TypeError("Object.unobserve cannot unobserve non-object"); 513 | 514 | if (typeof handler !== "function") 515 | throw new TypeError("Object.unobserve cannot deliver to non-function"); 516 | 517 | var hdata = handlers.get(handler), odata; 518 | 519 | if (hdata && (odata = hdata.observed.get(object))) { 520 | hdata.observed.forEach(function(odata, object) { 521 | performPropertyChecks(odata.data, object); 522 | }); 523 | nextFrame(function() { 524 | deliverHandlerRecords(hdata, handler); 525 | }); 526 | 527 | // In Firefox 13-18, size is a function, but createMap should fall 528 | // back to the shim for those versions 529 | if (hdata.observed.size === 1 && hdata.observed.has(object)) 530 | handlers["delete"](handler); 531 | else hdata.observed["delete"](object); 532 | 533 | if (odata.data.handlers.size === 1) 534 | observed["delete"](object); 535 | else odata.data.handlers["delete"](handler); 536 | } 537 | 538 | return object; 539 | }; 540 | 541 | /** 542 | * @function Object.getNotifier 543 | * @see http://arv.github.io/ecmascript-object-observe/#GetNotifier 544 | * @param {Object} object 545 | * @throws {TypeError} 546 | * @returns {Notifier} 547 | */ 548 | O.getNotifier = function getNotifier(object) { 549 | if (object === null || typeof object !== "object" && typeof object !== "function") 550 | throw new TypeError("Object.getNotifier cannot getNotifier non-object"); 551 | 552 | if (O.isFrozen && O.isFrozen(object)) return null; 553 | 554 | return retrieveNotifier(object); 555 | }; 556 | 557 | /** 558 | * @function Object.deliverChangeRecords 559 | * @see http://arv.github.io/ecmascript-object-observe/#Object.deliverChangeRecords 560 | * @see http://arv.github.io/ecmascript-object-observe/#DeliverChangeRecords 561 | * @param {Handler} handler 562 | * @throws {TypeError} 563 | */ 564 | O.deliverChangeRecords = function deliverChangeRecords(handler) { 565 | if (typeof handler !== "function") 566 | throw new TypeError("Object.deliverChangeRecords cannot deliver to non-function"); 567 | 568 | var hdata = handlers.get(handler); 569 | if (hdata) { 570 | hdata.observed.forEach(function(odata, object) { 571 | performPropertyChecks(odata.data, object); 572 | }); 573 | deliverHandlerRecords(hdata, handler); 574 | } 575 | }; 576 | 577 | })(Object, Array, this); 578 | --------------------------------------------------------------------------------