var divBounds = textDiv.getBoundingClientRect();
// create two so we can add some FX
var canvasA = document.createElement("canvas"); // this one is for display
var canvasB = document.createElement("canvas");
canvasB.width = canvasA.width = divBounds.width;
canvasB.height = canvasA.height = divBounds.height;
canvasA.style.position = "absolute";
canvasA.style.top = divBounds.top + "px"
canvasA.style.left = divBounds.left + "px"
var ctxA = canvasA.getContext("2d");
var ctxB = canvasB.getContext("2d");
document.body.appendChild(canvasA);
//--------------------------------------------------------------------
// Mouse and touch interface
//---------------------------------------------------------------------
const input = (function(){
var deviceIO;
// use navigator.maxTouchPoints to discover if device has touch caps.
if(navigator.maxTouchPoints > 0){
deviceIO = function(element){
const canvasPixelScale = 1; // not used
var top = 0; // offset to element
var left = 0;
var touch = {
x : 0, // primary touch point
y : 0,
points : [], // all touch points
count : 0, // count of active touches.
isTouched : false, // true if touched
events : "touchstart,touchmove,touchend,touchcancel".split(","),
}
// aliases for the lazy programmer
var t = touch;
var TP = touch.points;
// functions to track touch points
// Rather than create new touch points all the time this code uses a pre allocated array of touch points
// to track individual touch points.
function newTouch(){ // returns a new (empty) touch
for(var j = 0; j < touch.pCount; j ++){if(TP[j].id === -1){return TP[j]}}
}
function getTouch(id){ // returns a touch by its id
for(var j = 0; j < touch.pCount; j ++) {if (TP[j].id === id) {return TP[j]}}
}
// sets the coordinates of a touch point
function setTouch(touchPoint, touchItem, ending){
if(!ending){
touchPoint.dx = touchItem.pageX / canvasPixelScale - touchPoint.x;
touchPoint.dy = touchItem.pageY / canvasPixelScale - touchPoint.y;
}
touchPoint.x = (touchItem.pageX / canvasPixelScale) - left;
touchPoint.y = (touchItem.pageY / canvasPixelScale) - top;
}
// handle all touch events
function touchEvent(event){
event.preventDefault();
var tp;
var e = event;
var cT = event.changedTouches;
// ensure page offset is correct by updating it each event
var bounds = element.getBoundingClientRect()
left = bounds.left + scrollX;
top = bounds.top + scrollY;
if(event.type === "touchstart"){
for(var i = 0; i < cT.length; i ++){
var tp = newTouch();
setTouch(tp,cT[i]);
tp.dx = 0;
tp.dy = 0;
tp.id = cT[i].identifier;
}
}else if(event.type === "touchmove"){
for(var i = 0; i < cT.length; i ++){
setTouch(getTouch(cT[i].identifier),cT[i]);
}
}else if(event.type === "touchend"){
for(var i = 0; i < cT.length; i ++){
setTouch(tp = getTouch(cT[i].identifier),cT[i],true);
tp.id = -1;
}
}else if(event.type === "touchcancel"){
for(var i = 0; i < cT.length; i ++){
var tp = getTouch(cT[i].identifier);
tp.id = -1;
}
}
//check for any active touch events. If none turn off touch flag
touch.isTouched = false;
touch.count = 0;
for(var j = 0; j < touch.pCount; j ++) {
if (TP[j].id !== -1) {
if(touch.count === 0){ // use the first touch point as main input coord
touch.x = TP[j].x;
touch.y = TP[j].y;
}
touch.isTouched = true;
touch.count += 1;
}
}
return false;
}
touch.pCount = navigator.maxTouchPoints;
for(var i = 0; i < touch.pCount; i ++){// create a set of touch points that persist
touch.points[i] = { x : 0,y : 0,dx : 0,dy : 0,down : false,id : -1,}
}
touch.events.forEach(n => { element.addEventListener(n, touchEvent); } );
return touch;
}
}else{ // use the mouse instead
deviceIO = function(element){
function preventDefault(e) { e.preventDefault(); }
var i;
var mouse = {
x : 0, y : 0, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover".split(",")
};
function mouseMove(e) {
var t = e.type;
var m = mouse;
var bounds = element.getBoundingClientRect()
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true; }
e.preventDefault();
}
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
return mouse;
}
}
return deviceIO;
}());
const io = input(canvasA); // start user input
//------------------------------------------------------------------
// Scratchy
//------------------------------------------------------------------
const message = "Touch!"; // message on canvas
const font = "68px arial black"; // text for message
const fontCol = "#777"; // colour off message
// the display settings for app
const drawWidth = 40; // radius of touch area
const edgeStyle = "#777"; // colour of overlay
const edgeWidth = 4; // make look nice
const overlayStyle = "#999"; // colour of overlay
const displayAccentMain = "rgba(80,160,255,1)"; // make look nice
const displayAccent = "rgba(60,60,60,1)";
const touchPointFalloffA = 1.1; // shift center of s curve. Less than one and like touch press harder more than one and make touch more like feather scratching glass
const touchPointFalloffB = 2; // strength off s curve
var lastX;
var lastY; // save the position of last touch mouse
const touchStyle = createTouchStyle(touchPointFalloffA, touchPointFalloffB); // creates a gradient used to draw the touch point
start(); // start the app;
function createTouchStyle(a,b){
const touchGradient = ctxA.createRadialGradient(0,0,0,0,0,drawWidth);
const curveS = (x,p) => {
x = x < 0 ? 0 : x > 1 ? 1 : x;
var xx = Math.pow(x,p);
return xx / (xx + Math.pow(1-x,p));
};
const curveB = (x,p) => {
x = x < 0 ? 0 : x > 1 ? 1 : x;
return Math.pow(x,p);
};
for(var i = 0; i <= 1; i += 0.025){
touchGradient.addColorStop(i,"rgba(0,0,0,"+curveS(curveB(1-i,a),b)+")");
}
return touchGradient;
}
// Draw the starting canvas
function start() {
ctxB.font = font;
ctxB.textAlign = "center";
ctxB.textBaseline = "middle";
ctxB.fillStyle = edgeStyle;
ctxB.fillRect(0, 0, canvasA.width, canvasA.height);
ctxB.fillStyle = overlayStyle;
ctxB.fillRect(edgeWidth, edgeWidth, canvasA.width - edgeWidth * 2, canvasA.height - edgeWidth * 2);
ctxB.fillStyle = fontCol;
ctxB.fillText(message,canvasB.width / 2, canvasB. height / 2);
ctxA.drawImage(canvasB, 0, 0); // put canvas on display
textDiv.className = "showText"; // set color of text so that it can be read
update();
}
// update the canvas when there is touch or mouse changes
function update() {
// only on touch or mouse button down
if (io.isTouched || io.buttonRaw === 1) {
if(lastX === undefined){ // if start of touch set last to current
lastX = io.x;
lastY = io.y;
}
// set gradient and copmosite mode
ctxB.fillStyle = touchStyle;
ctxB.globalCompositeOperation = "destination-out";
// If large movement then smear the couch over the distance
// Find the distance from last contact point / mouse down
var dx = io.x - lastX;
var dy = io.y - lastY;
var dist = Math.sqrt(dx*dx+dy*dy);
if(dist > 1.5){ // only smear out contact if dist over 1.5 pixels
lastX += (dx /= dist); // normalise vector between contacts
lastY += (dy /= dist); // also step past last pos as that has been drawn
dist += 1;
ctxB.globalAlpha = Math.max(0.05,1 / dist); // reduce FX depending on dist
while(dist > 0){ // move from last to current
ctxB.beginPath();
ctxB.setTransform(1,0,0,1,lastX,lastY);
ctxB.arc(0, 0, drawWidth, 0, Math.PI * 2);
ctxB.fill();
lastX += dx;
lastY += dy;
dist -= 1;
}
}else{
// just a single touch point
ctxB.globalAlpha = 1.0;
ctxB.setTransform(1,0,0,1,io.x,io.y);
ctxB.beginPath();
ctxB.arc(0, 0, drawWidth, 0, Math.PI * 2);
ctxB.fill();
}
// save this pos for the next frame
lastX = io.x;
lastY = io.y;
// draw to display canvas using shadows to add FX
ctxA.clearRect(0,0,canvasA.width,canvasA.height);
// Add to display canvas with very slight highlight
ctxA.shadowOffsetX = ctxA.shadowOffsetY = 1;
ctxA.shadowColor = displayAccentMain;
ctxA.drawImage(canvasB, 0, 0);
ctxA.shadowOffsetX = ctxA.shadowOffsetY = -1;
ctxA.shadowColor = displayAccent;
ctxA.drawImage(canvasB, 0, 0);
}else{
lastX = undefined; // no contact no last contact!
}
requestAnimationFrame(update);
}
div {
font-family : arial black;
font-size : 16px;
}
#textDiv {
width: 500px;
height: 400px;
padding: 4px;
border: 4px #fff solid;
box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
}
.hideText {
background: #999;
color: #999;
}
.showText { /* because the canvas is added after the div is displayed */
/* the above style hides the text and this make is visible */
/* when canvas is ready */
background: #000;
color: #fff;
}
<div>
<h2>Test touch</h2>
<div id="textDiv" class="hideText">
<h3>From MDN TouchEvent</h3>
<p>The TouchEvent interface represents an event sent when the state of contacts with a touch-sensitive surface changes. This surface can be a touch screen or trackpad, for example. The event can describe one or more points of contact with the screen and includes support for detecting movement, addition and removal of contact points, and so forth.</p>
<p>Touches are represented by the Touch object; each touch is described by a position, size and shape, amount of pressure, and target element. Lists of touches are represented by TouchList objects</p>
</div>
</div>