UNPKG

scratchcard

Version:
450 lines (345 loc) 10.1 kB
/* jshint browserify: true */ 'use strict'; var EventEmitter = require('events').EventEmitter; var util = require('util'); var defaults = require('lodash.defaults'); var Painter = Scratchcard.Painter = require('./painter'); module.exports = exports = Scratchcard; var defaultOptions = { threshold: 255, realtime: false, layers: 0, painter: {}, animationFrame: null }; function Scratchcard(element, options) { if (isIeLessThan9()) { throw new Error('Internet Explorer not supported prior to version 9'); } EventEmitter.call(this); var self = this; var canvas = document.createElement('canvas'); if (!canvas.getContext) { throw new Error('HTML5 canvas not supported'); } var ctx = canvas.getContext('2d'); options = (options instanceof Painter) ? defaults({painter: options}, defaultOptions) : defaults({}, options, defaultOptions); var requestAnimationFrame = window.requestAnimationFrame; var animationFrame = options.animationFrame; if (animationFrame && animationFrame.request) { requestAnimationFrame = animationFrame.request.bind(animationFrame); } if (!requestAnimationFrame) { throw new Error('requestAnimationFrame not supported'); } var size = getSize(element); canvas.style.position = 'absolute'; canvas.width = size.width; canvas.height = size.height; canvas.style.zIndex = options.layers + 1; // Disable the blue overlay for some browsers canvas.style['-webkit-tap-highlight-color'] = 'rgba(0,0,0,0)'; var wrapper = document.createElement('div'); wrapper.className = 'scratchcard'; wrapper.style.position = 'relative'; wrapper.style.display = 'inline'; wrapper.style.verticalAlign = 'top'; wrapper.appendChild(canvas); var layers = []; for (var index = 0; index < options.layers; index++) { var layer = document.createElement('canvas'); layer.style.position = 'absolute'; layer.width = size.width; layer.height = size.height; layer.style.zIndex = index + 1; layer.style.pointerEvents = 'none'; layers.push({ canvas: layer, ctx: layer.getContext('2d') }); wrapper.appendChild(layer); } var painter = null; var previousFingers = {}; var previousFingersCount = 0; var currentFingers = {}; var currentFingersCount = 0; var updated = false; var progress = null; var completed = false; var animated = false; this.getElement = getElement; this.setElement = setElement; this.getPainter = getPainter; this.setPainter = setPainter; this.getWrapper = getWrapper; this.getCanvas = getCanvas; this.reset = reset; this.complete = complete; this.getProgress = getProgress; setPainter(options.painter); if (element) { element.parentNode.insertBefore(wrapper, element); element.style.visibility = 'visible'; canvas.addEventListener('mousedown', onMouseDown, true); canvas.addEventListener('touchstart', onTouchStart, true); animate(); } function getElement() { return element; } function setElement(newElement) { if (newElement) { element = newElement; synchronize(); canvas.addEventListener('mousedown', onMouseDown, true); canvas.addEventListener('touchstart', onTouchStart, true); if (!animated) { animate(); } } else if (element) { element = null; wrapper.parentNode.removeChild(wrapper); canvas.removeEventListener('mousedown', onMouseDown, true); canvas.removeEventListener('touchstart', onTouchStart, true); } } function getPainter() { return painter; } function setPainter(newPainter) { painter = (newPainter instanceof Painter) ? newPainter : new Painter(newPainter); redraw(); } function getWrapper() { return wrapper; } function getCanvas() { return canvas; } function reset() { painter.reset(ctx, canvas.width, canvas.height); updated = true; completed = false; canvas.style.pointerEvents = null; checkProgress(); } function drawPoint(point) { painter.drawPoint(ctx, point); updated = true; } function drawLine(start, end) { painter.drawLine(ctx, start, end); updated = true; } function complete() { painter.complete(ctx, canvas.width, canvas.height); updated = true; completed = true; canvas.style.pointerEvents = 'none'; checkProgress(); } function redraw() { if (completed) { painter.complete(ctx, canvas.width, canvas.height); } else { painter.reset(ctx, canvas.width, canvas.height); } updated = true; for (var index = 0, count = layers.length; index < count; index++) { var layer = layers[index]; painter.drawLayer(layer.ctx, layer.canvas.width, layer.canvas.height, index); } checkProgress(); } function getProgress() { var width = canvas.width; var height = canvas.height; var pixels = width * height; if (pixels === 0) { return 0; } var holes = 0; var data = ctx.getImageData(0, 0, width, height).data; for (var index = 3, count = data.length; index < count; index += 4) { if (data[index] >= options.threshold) { holes++; } } return (pixels - holes) / pixels; } function checkProgress() { if (!updated) { return; } var lastProgress = progress; progress = getProgress(); updated = false; if (progress !== lastProgress) { self.emit('progress', progress); if ((progress === 1) && (!completed)) { complete(); } } } function synchronize() { var parent = element.parentNode; if (!parent) { setElement(null); return; } if (wrapper.nextSibling !== element) { parent.insertBefore(wrapper, element); } var size = getSize(element); if ((canvas.width === size.width) && (canvas.height === size.height)) { return; } canvas.width = size.width; canvas.height = size.height; for (var index = 0, count = layers.length; index < count; index++) { var layer = layers[index].canvas; layer.width = size.width; layer.height = size.height; } redraw(); } function animate() { if (!element) { animated = false; return; } requestAnimationFrame(animate); animated = true; synchronize(); for (var identifier in currentFingers) { var previousFinger = previousFingers[identifier]; var currentFinger = currentFingers[identifier]; if (!previousFinger) { drawPoint(currentFinger); } else if ((currentFinger.x !== previousFinger.x) || (currentFinger.y !== previousFinger.y)) { drawLine(previousFinger, currentFinger); } } if (options.realtime || ((currentFingersCount === 0) && (previousFingersCount > 0))) { checkProgress(); } previousFingers = currentFingers; previousFingersCount = currentFingersCount; } function onMouseDown(event) { if (event.button !== 0) { return; } event.preventDefault(); // console.log('mousedown'); var boundingRect = canvas.getBoundingClientRect(); currentFingers = { mouse: { x: event.clientX - boundingRect.left, y: event.clientY - boundingRect.top } }; currentFingersCount = 1; window.addEventListener('mousemove', onMouseMove, true); window.addEventListener('mouseup', onMouseUp, true); canvas.removeEventListener('mousedown', onMouseDown, true); canvas.removeEventListener('touchstart', onTouchStart, true); } function onMouseMove(event) { if (event.button !== 0) { return; } event.preventDefault(); // console.log('mousemove'); var boundingRect = canvas.getBoundingClientRect(); currentFingers = { mouse: { x: event.clientX - boundingRect.left, y: event.clientY - boundingRect.top } }; currentFingersCount = 1; } function onMouseUp(event) { if (event.button !== 0) { return; } event.preventDefault(); // console.log('mouseup'); currentFingers = {}; currentFingersCount = 0; window.removeEventListener('mousemove', onMouseMove, true); window.removeEventListener('mouseup', onMouseUp, true); if (element) { canvas.addEventListener('mousedown', onMouseDown, true); canvas.addEventListener('touchstart', onTouchStart, true); } } function onTouchStart(event) { event.preventDefault(); // console.log('touchstart'); currentFingers = {}; currentFingersCount = event.touches.length; var boundingRect = canvas.getBoundingClientRect(); for (var index = 0; index < currentFingersCount; index++) { var touch = event.touches[index]; currentFingers[touch.identifier] = { x: touch.clientX - boundingRect.left, y: touch.clientY - boundingRect.top }; } window.addEventListener('touchstart', onTouch, true); window.addEventListener('touchmove', onTouch, true); window.addEventListener('touchend', onTouch, true); canvas.removeEventListener('mousedown', onMouseDown, true); canvas.removeEventListener('touchstart', onTouchStart, true); } function onTouch(event) { event.preventDefault(); // console.log(event.type); currentFingers = {}; currentFingersCount = event.touches.length; if (currentFingersCount > 0) { var boundingRect = canvas.getBoundingClientRect(); for (var index = 0; index < currentFingersCount; index++) { var touch = event.touches[index]; currentFingers[touch.identifier] = { x: touch.clientX - boundingRect.left, y: touch.clientY - boundingRect.top }; } } else { window.removeEventListener('touchstart', onTouch, true); window.removeEventListener('touchmove', onTouch, true); window.removeEventListener('touchend', onTouch, true); if (element) { canvas.addEventListener('mousedown', onMouseDown, true); canvas.addEventListener('touchstart', onTouchStart, true); } } } } util.inherits(Scratchcard, EventEmitter); function isIeLessThan9() { var div = document.createElement('div'); div.innerHTML = '<!--[if lt IE 9]><i></i><![endif]-->'; return (div.getElementsByTagName('i').length === 1); } var zero = {width: 0, height: 0}; function getSize(element) { if (!element) { return zero; } var rects = element.getClientRects(); if (rects.length === 0) { return zero; } var firstRect = rects[0]; return { width: Math.ceil(firstRect.right) - Math.floor(firstRect.left), height: Math.ceil(firstRect.bottom) - Math.floor(firstRect.top) }; }