From 277677170d5ad972fa6b587fced34b498ac74bb4 Mon Sep 17 00:00:00 2001 From: Gar Date: Sat, 25 Feb 2017 14:42:43 -0500 Subject: [PATCH] feat(proto): workable version --- .travis.yml | 17 ++++ index.html | 58 +++++++++++++ package.json | 70 +++++++++++++++ server.js | 15 ++++ src/Capture.js | 138 ++++++++++++++++++++++++++++++ src/DragFile.js | 34 ++++++++ src/Helper.js | 11 +++ src/Render.js | 86 +++++++++++++++++++ src/dev.js | 112 ++++++++++++++++++++++++ static/styles.css | 50 +++++++++++ styles.css | 84 ++++++++++++++++++ webpack.config.babel.js | 20 +++++ webpack.config.dev.js | 25 ++++++ webpack.config.utilities.babel.js | 20 +++++ 14 files changed, 740 insertions(+) create mode 100644 .travis.yml create mode 100644 index.html create mode 100644 package.json create mode 100644 server.js create mode 100644 src/Capture.js create mode 100644 src/DragFile.js create mode 100644 src/Helper.js create mode 100644 src/Render.js create mode 100644 src/dev.js create mode 100644 static/styles.css create mode 100644 styles.css create mode 100644 webpack.config.babel.js create mode 100644 webpack.config.dev.js create mode 100644 webpack.config.utilities.babel.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..730edbe --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: node_js +cache: + directories: + - node_modules +notifications: + email: false +node_js: + - '7' + - '6' + - '4' +before_script: + - npm prune +after_success: + - npm run semantic-release +branches: + except: + - /^v\d+\.\d+\.\d+$/ diff --git a/index.html b/index.html new file mode 100644 index 0000000..c2d1514 --- /dev/null +++ b/index.html @@ -0,0 +1,58 @@ + + + + + Handwrite + + + + + + + +
+ Drag Image Here +
+ + + + +
+ +
+

Sign Here

+ +
+ + +
+ + + + + + + +
+ +
+

Preview Animation Here

+ + +
+ + +
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..9ab5687 --- /dev/null +++ b/package.json @@ -0,0 +1,70 @@ +{ + "name": "handwrite", + "version": "0.0.0-development", + "description": "creates masking effect that looks like handwriting", + "main": "index.js", + "scripts": { + "start": "node server.js", + "commit": "git-cz", + "watch:test": "jest --watch", + "test": "jest", + "prebuild": "rimraf dist", + "build": "npm-run-all --parallel build:*", + "build:main": "babel src", + "build:umd": "webpack --config webpack.config.babel.js --output-filename index.umd.js", + "build:umd.min": "webpack --config webpack.config.babel.js --output-filename index.umd.min.js -p", + "build:utilities": "webpack --config webpack.config.utilities.babel.js --output-filename utilities.umd.min.js -p", + "semantic-release": "semantic-release pre && npm publish && semantic-release post" + }, + "repository": { + "type": "git", + "url": "https://github.com/lonelydatum/handwrite.git" + }, + "keywords": [ + "handwriting", + "mask" + ], + "devDependencies": { + "babel-cli": "6.22.2", + "babel-jest": "18.0.0", + "babel-loader": "6.2.10", + "babel-polyfill": "6.22.0", + "babel-preset-es2015": "6.22.0", + "babel-preset-stage-2": "6.22.0", + "babel-preset-stage-3": "6.22.0", + "chai": "3.5.0", + "commitizen": "2.9.5", + "cz-conventional-changelog": "1.2.0", + "jest-cli": "18.1.0", + "mocha": "3.2.0", + "npm-run-all": "4.0.1", + "rimraf": "2.5.4", + "semantic-release": "^6.3.2", + "webpack": "2.2.1", + "webpack-dev-server": "2.2.1" + }, + "config": { + "commitizen": { + "path": "node_modules/cz-conventional-changelog" + } + }, + "babel": { + "presets": [ + "es2015", + "stage-2", + "stage-3" + ] + }, + "jest": { + "testEnvironment": "jsdom" + }, + "dependencies": { + "signals": "1.0.0" + }, + "author": "Gar Liu (http://lonelydatum.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/lonelydatum/handwrite/issues" + }, + "homepage": "https://github.com/lonelydatum/handwrite#readme" +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..af79fe5 --- /dev/null +++ b/server.js @@ -0,0 +1,15 @@ +var webpack = require('webpack'); +var WebpackDevServer = require('webpack-dev-server'); +var config = require('./webpack.config.dev'); + +new WebpackDevServer(webpack(config), { + publicPath: config.output.publicPath, + hot: true, + historyApiFallback: true +}).listen(1337, 'localhost', function (err, result) { + if (err) { + console.log(err); + } + + console.log('Listening at localhost:1337'); +}); \ No newline at end of file diff --git a/src/Capture.js b/src/Capture.js new file mode 100644 index 0000000..548699e --- /dev/null +++ b/src/Capture.js @@ -0,0 +1,138 @@ +import {Circle} from './Helper' +import Signals from 'signals' + + +class Capture { + constructor(canvas, radius=20) { + this.signals = { + pointsUpdated: new Signals() + } + + this.radius = radius + this.isDown = false + this.canvas = canvas + this.ctx = this.canvas.getContext('2d') + + this.canvas.addEventListener('mousemove', this.onMove.bind(this), false ) + this.canvas.addEventListener('mousedown', this.onDown.bind(this), false ) + this.canvas.addEventListener('mouseup', this.onUp.bind(this), false ) + + this.points = [] + + this.index = 0 + + this.tl = new TimelineMax() + this.color = 'rgba(255, 0, 255, .2)' + this.prev = {x:-1, y:-1} + + this.storage = [] + this.currentItem = [] + + document.onkeydown = this.onKeyPress.bind(this, this); + } + + setBrush(value) { + this.radius = value + } + + + // undo from key: ctr+z + onKeyPress(scope, e) { + var evtobj = window.event? event : e + if (evtobj.keyCode == 90 && evtobj.ctrlKey) { + scope.undo() + } + } + + + undo(){ + this.storage.splice(-1, 1) + this.merge() + this.updateDraw() + } + + + // takes all the items from this.storage and merges/flattens them into this.points + merge() { + this.points = [] + this.storage.forEach(item=>{ + this.points = this.points.concat(item) + }) + + localStorage.setItem('points', JSON.stringify(this.points)) + this.signals.pointsUpdated.dispatch(this.points) + } + + + + onDown(e) { + e.preventDefault() + this.currentItem = [] + this.isDown = true + } + + onUp(e) { + this.isDown = false + this.storage.push(this.currentItem) + this.merge() + } + + + + onMove(e){ + if(!this.isDown) { return } + + + const pos = this.getMousePos(this.canvas, e) + if(pos.x!==this.prev.x|| pos.y!==this.prev.y) { + this.currentItem.push(pos) + this.prev = pos + this.drawCircle(pos.x, pos.y) + } + } + + drawCircle(x, y) { + Circle(this.ctx, x, y, this.radius, this.color) + } + + updateDraw() { + this.ctx.clearRect(0,0,this.canvas.width, this.canvas.height) + this.points.forEach(pos => this.drawCircle(pos.x, pos.y) ) + + } + + startOver() { + this.storage = [] + this.merge() + this.updateDraw() + } + + // clear() { + // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) + // } + + draw() { + this.index = 0 + const si = setInterval(()=>{ + const point = this.points[this.index] + if(point) { + // Circle(this.ctx, point.x, point.y, this.radius, '#00FF00') + } else { + clearInterval(si) + } + this.index++ + }, 10) + } + + + + getMousePos(canvas, evt) { + var rect = canvas.getBoundingClientRect() + return { + x: Math.round(evt.clientX - rect.left), + y: Math.round(evt.clientY - rect.top) + }; + } +} + +export default Capture \ No newline at end of file diff --git a/src/DragFile.js b/src/DragFile.js new file mode 100644 index 0000000..c67a863 --- /dev/null +++ b/src/DragFile.js @@ -0,0 +1,34 @@ +import Signals from 'signals' + +class DragFile { + constructor(dom) { + this.signals = { + imageReady: new Signals() + } + var holder = dom + holder.ondragleave = function () { this.className = ''; return false; }; + holder.ondragover = function () { this.className = 'hover'; return false; }; + holder.ondragend = function () { this.className = ''; return false; }; + holder.ondrop = this.onDrop.bind(holder, this) + } + + onDrop(scope, e) { + this.className = ''; + e.preventDefault(); + + var file = e.dataTransfer.files[0] + const reader = new FileReader(); + reader.onload = (event) => { + scope.signals.imageReady.dispatch(event.target.result) + }; + + + reader.readAsDataURL(file); + return false; + + } + + +} + +export default DragFile \ No newline at end of file diff --git a/src/Helper.js b/src/Helper.js new file mode 100644 index 0000000..229fca5 --- /dev/null +++ b/src/Helper.js @@ -0,0 +1,11 @@ +const PI = Math.PI +// const color = '#ff0000' + +function Circle(ctx, x, y, r=8, color='#ff0000') { + ctx.beginPath(); + ctx.arc(x, y, r, 0, 2 * PI, false); + ctx.fillStyle = color; + ctx.fill(); +} + +export {Circle} \ No newline at end of file diff --git a/src/Render.js b/src/Render.js new file mode 100644 index 0000000..3f27a5d --- /dev/null +++ b/src/Render.js @@ -0,0 +1,86 @@ +import {Circle} from './Helper' + +class Render { + constructor(canvas, image) { + + this.image = image + this.canvasArt = canvas + this.canvasMask = this.canvasArt.cloneNode() + // document.body.appendChild(this.canvasMask) + this.ctxArt = this.canvasArt.getContext('2d') + this.ctxMask = this.canvasMask.getContext('2d') + + this.WIDTH = this.canvasArt.width + this.HEIGHT = this.canvasArt.height + } + + draw(points, options) { + this.clear() + const defaultOptions = {timeScale:1, radius:6} + const o = {...defaultOptions, ...options} + const {timeScale, radius} = o + this.tl = new TimelineMax() + const tlDraw = new TimelineMax({onComplete:this.onDone.bind(this)}) + tlDraw.timeScale(timeScale) + + let size = 0 + points.map(item=>{ + tlDraw.addCallback(()=>{ + Circle(this.ctxMask, item.x, item.y, radius) + size++ + }, "+=.01") + }) + + this.tl.add(tlDraw) + + this.tl.add('final') + // this.tl.to(this.canvasArt, .001, {opacity:0}, 'final') + // this.tl.to(this.image, .001, {opacity:1}, 'final') + + + this.keepRendering = true + this.render() + return this.tl + + } + + clear() { + this.ctxArt.clearRect(0,0,this.WIDTH, this.HEIGHT) + this.ctxMask.clearRect(0,0,this.WIDTH, this.HEIGHT) + } + + + onDone() { + // this.keepRendering = false + // this.drawArt() + } + + + drawArt() { + + this.ctxArt.clearRect(0, 0, this.WIDTH, this.HEIGHT) + this.ctxArt.drawImage(this.canvasMask, 0, 0); + + this.ctxArt.save(); + + this.ctxArt.globalCompositeOperation = 'source-in'; + + this.ctxArt.drawImage(this.image, 0, 0); + + this.ctxArt.restore(); + + } + + render() { + + this.drawArt() + + if(!this.keepRendering) { + return + } + requestAnimationFrame(this.render.bind(this)) + + } +} + +export default Render \ No newline at end of file diff --git a/src/dev.js b/src/dev.js new file mode 100644 index 0000000..9f15d56 --- /dev/null +++ b/src/dev.js @@ -0,0 +1,112 @@ +import DragFile from './DragFile.js' +import Capture from './Capture.js' +import Render from './Render.js' + + +const dragTarget = document.getElementById('dragTarget'); +const captureCanvas = document.getElementById('captureCanvas'); +const captureImage = document.getElementById('captureImage'); +const pointsTextArea = document.getElementById('pointsTextArea'); + +const renderImage = document.getElementById('renderImage'); +const renderCanvas = document.getElementById('renderCanvas'); + +const brushSizeResult = document.getElementById('brushSizeResult'); +const brushSize = document.getElementById('brushSize'); + +const undo = document.getElementById('undo'); +const totalPoint = document.getElementById('totalPoint'); + +const startOver = document.getElementById('startOver'); + + + + +class Dev { + constructor() { + this.render = null + + brushSize.addEventListener('change', this.brushUpdate.bind(this)) + this.capture = new Capture(captureCanvas, brushSize.value) + this.brushUpdate() + + + this.dragFile = new DragFile(dragTarget) + this.dragFile.signals.imageReady.add((imageData)=>{ + this.loadImage(imageData); + }) + + const imageData = localStorage.getItem('image') + if(imageData) { + this.loadImage(imageData) + } + + + const drawButton = document.getElementById('drawButton'); + drawButton.addEventListener('click', ()=>{ + this.draw(this.capture.points) + }) + + undo.addEventListener('click', this.undo.bind(this)) + + startOver.addEventListener('click', this.capture.startOver.bind(this.capture) ) + + this.pointsUpdated([]) + + } + + undo() { + this.capture.undo() + } + + brushUpdate() { + this.capture.setBrush(brushSize.value) + brushSizeResult.innerHTML = `Brush size of: ${brushSize.value}` + } + + loadImage(imageData) { + localStorage.setItem('image', imageData) + renderImage.src = captureImage.src = imageData + renderCanvas.width = captureCanvas.width = captureImage.width + renderCanvas.height = captureCanvas.height = captureImage.height + + this.capture.signals.pointsUpdated.add(this.pointsUpdated) + + this.render = new Render(renderCanvas, renderImage) + } + + + draw(points) { + this.render.draw(points, {radius:this.capture.radius}) + } + + + pointsUpdated(points) { + pointsTextArea.value = JSON.stringify(points) + totalPoint.innerHTML = `Length of array ${points.length}` + localStorage.setItem('points', pointsTextArea.value) + } + +} + + + + + + + + + + + + + + + + + + + + + +export default new Dev() diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..9c724c5 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,50 @@ + +body { + background-color: #333; + font-family: 'Abel', sans-serif; + +} + +button { + border: 2px solid white; + background-color: transparent; + padding: 6px 16px; + color: white; + font-size: 22px; + display: block; +} + +#signature { + display: flex; + max-width: 900px; + margin: 0 auto; + color: white; + font-size: 20px; + +} + +.column { + border: 1px solid green; + flex: 1; +} + + +/* +#capture { + .holder { + position: relative; + img{ + opacity: .3; + } + canvas { + border: 1px solid red; + position: absolute; + left:0; + top:0; + } + } + p.points{ + color: pink; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + } +}*/ \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..8a79793 --- /dev/null +++ b/styles.css @@ -0,0 +1,84 @@ + +body { + background-color: #333; + font-family: 'Abel', sans-serif; + +} + +button { + border: 2px solid white; + background-color: transparent; + padding: 6px 16px; + color: white; + font-size: 22px; + display: block; + cursor: pointer; +} + +#dragTarget { + border: 10px dashed #ccc; + width: 600px; + height: 200px; + margin: 20px auto; + display:flex; + justify-content: center; + align-items: center; +} +#dragTarget.hover { + border: 10px dashed #FF0000; +} + +.holder { + position: relative; +} + +canvas{ + border: 1px solid yellow; + position: absolute; + top: 0; + left: 0; +} + +#renderItem img { + opacity: 0; +} + +#signature { + display: flex; + min-width: 900px; + max-width: 1200px; + margin: 0 auto; + color: white; + font-size: 20px; + +} + +.column { + border: 1px solid green; + flex: 1; +} + +#brushSize { + width: 300px; +} + + +/* +#capture { + .holder { + position: relative; + img{ + opacity: .3; + } + canvas { + border: 1px solid red; + position: absolute; + left:0; + top:0; + } + } + p.points{ + color: pink; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + } +}*/ \ No newline at end of file diff --git a/webpack.config.babel.js b/webpack.config.babel.js new file mode 100644 index 0000000..dd432ce --- /dev/null +++ b/webpack.config.babel.js @@ -0,0 +1,20 @@ +import {join} from 'path' + +const include = join(__dirname, 'src') + +export default { + entry: './src/FrecklesHack.js', + output: { + path: join(__dirname, 'dist'), + libraryTarget: 'umd', + library: 'Freckles' + }, + devtool: 'source-map', + module: { + loaders: [ + {test: /\.js$/, loader: 'babel-loader', include}, + {test: /\.json$/, 'loader': 'json-loader', include}, + ] + } +} + diff --git a/webpack.config.dev.js b/webpack.config.dev.js new file mode 100644 index 0000000..583fb56 --- /dev/null +++ b/webpack.config.dev.js @@ -0,0 +1,25 @@ +var path = require('path'); +var webpack = require('webpack'); + +module.exports = { + entry: [ + 'webpack-dev-server/client?http://localhost:1337', + 'webpack/hot/dev-server', + './src/dev.js' + ], + output: { + path: __dirname, + filename: 'bundle.js', + publicPath: '/static/' + }, + devtool: 'source-map', + module: { + loaders: [ + { test: /\.js$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') } + ] + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin() + ] +}; \ No newline at end of file diff --git a/webpack.config.utilities.babel.js b/webpack.config.utilities.babel.js new file mode 100644 index 0000000..3e9c930 --- /dev/null +++ b/webpack.config.utilities.babel.js @@ -0,0 +1,20 @@ +import {join} from 'path' + +const include = join(__dirname, 'src') + +export default { + entry: './src/util/Utility.js', + output: { + path: join(__dirname, 'dist'), + libraryTarget: 'umd', + library: 'FrecklesUtility', + }, + devtool: 'source-map', + module: { + loaders: [ + {test: /\.js$/, loader: 'babel-loader', include}, + {test: /\.json$/, 'loader': 'json-loader', include}, + ] + } +} +