<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>MT Request: draw over the image</title> <style media="screen"> #sample, svg{ position:absolute; top: 0; left: 0; bottom: 0; right:0; width:100%; height:100%; font-family: sans-serif; z-index:2; } img{ position:absolute; top:0; bottom:0; right:0; left:0; width:100%; height:100%; z-index:1; } path { fill: none; stroke: red; stroke-width: 3mm; } body.submitted path{ stroke:darkgray; } body.submitted .buttons{ display:none; } /*#wrapper { height: calc({DRAW_HEIGHT}/{HEIGHT} * 100%); width: calc({DRAW_WIDTH}/{WIDTH} * 100%); position: absolute; left: calc(({WIDTH} - {DRAW_WIDTH})/2/{WIDTH} * 100%); top: calc(({HEIGHT} - {DRAW_HEIGHT})/2/{HEIGHT} * 100%); background:none; cursor: url(cursor.png) 6 6, auto; }*/ #wrapper { position:absolute; top:0; right:0; bottom:0; left:0; background:none; cursor: url(cursor.png) 6 6, auto; } .gray{ position:absolute; background:rgba(255,255,255,0.7); } #gray_top{ left:0; right:0; top:0; height:calc({TOP_PADDING}/{HEIGHT} * 100%); } #gray_bottom{ left:0; right:0; bottom:0; height:calc(({HEIGHT} - {DRAW_HEIGHT} - {TOP_PADDING})/{HEIGHT} * 100%); } #gray_left{ left:0; top:calc({TOP_PADDING}/{HEIGHT} * 100%); height: calc({DRAW_HEIGHT}/{HEIGHT} * 100%); width: calc({LEFT_PADDING}/{WIDTH} * 100%); } #gray_right{ right:0; top:calc({TOP_PADDING}/{HEIGHT} * 100%); height: calc({DRAW_HEIGHT}/{HEIGHT} * 100%); width: calc(({WIDTH} - {DRAW_WIDTH} - {LEFT_PADDING})/{WIDTH} * 100%); } html, body{ height: 100%; width: 100%; margin:0; background:gray; } #interface{ background:black; height: 0; overflow: hidden; padding-top: calc({HEIGHT}/{WIDTH} * 100%); position: relative; margin: 0 auto; background-size: 100% 100%; } #innerface{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; } @media (min-aspect-ratio: {WIDTH}/{HEIGHT}) { #interface { height: 100vh; width: calc({WIDTH}/{HEIGHT} * 100vh); padding-top:0; } } #info{ position: absolute; bottom: 15px; width: 600px; left: calc(50% - 250px); z-index: 999; } .buttons{ text-align: center; } #submit{ background: lightblue; border: solid 1px blue; border-radius: 5px; font-size: 110%; padding: 5px 10px; } </style> </head> <body> <div id='interface'"> <div id='innerface'> <div id='wrapper'> <!-- <img src="{IMAGE_URL}" id='sample'>--> <svg id="canvas" viewBox="0 0 {WIDTH}0 {HEIGHT}0" width="{WIDTH}mm" height="{HEIGHT}mm" preserveAspectRatio="none"> <path d="" id="stroke" /> </svg> <img src='{IMAGE_URL}'> </div> <div id='info'> <ul> <li>Drag the mouse to trace the lines drawing above</li> <li>Follow the lines as precise as possible</li> <li>Press submit when you're done.</li> <li>You'll receive a submission token, to fill in at Mechanical Turk</li> </ul> <div class='buttons'> <button id='submit'>Submit</button> <!-- <button id='reset'>Reset</button>--> </div> <div id='message'></div> </div> <div class='gray' id='gray_top'></div> <div class='gray' id='gray_bottom'></div> <div class='gray' id='gray_left'></div> <div class='gray' id='gray_right'></div> </div> </div> <script type="text/javascript"> let url = window.location.origin.replace('http', 'ws') +'/ws?' + window.location.search.substring(1); let svgEl = document.getElementById("canvas"); let strokeEl = document.getElementById('stroke'); let submitEl = document.getElementById('submit'); let resetEl = document.getElementById('reset'); let messageEl = document.getElementById('message'); let innerFaceEl = document.getElementById('innerface'); // wrapper within the interface let svgWidth = {WIDTH}; let svgHeight = {HEIGHT}; let drawWidth = {DRAW_WIDTH}; let drawHeight = {DRAW_HEIGHT}; let xPadding = {LEFT_PADDING} / svgWidth; let yPadding = {TOP_PADDING} / svgHeight; let drawWidthFactor = drawWidth / svgWidth; let drawHeightFactor = drawHeight / svgHeight; let strokes = []; let isDrawing = false; let hasMouseDown = false; let currentPoint = null; let getCoordinates = function(e) { // convert event coordinates into relative positions on x & y axis let box = innerFaceEl.getBoundingClientRect(); let x = (e.x - box['left']) / box['width']; let y = (e.y - box['top']) / box['height']; return {'x': x, 'y': y}; } let isInsideBounds = function(pos) { return !(pos['x'] < 0 || pos['y'] < 0 || pos['x'] > 1 || pos['y'] > 1); } let isInsideDrawingBounds = function(pos) { if(pos['x'] > xPadding && pos['x'] < (xPadding+drawWidthFactor) && pos['y'] > yPadding && pos['y'] < yPadding+drawHeightFactor) { return true; } return false; } let draw = function(e) { let pos = getCoordinates(e); if(!isInsideBounds(pos)) { // outside of bounds return; } if(isDrawing && !isInsideDrawingBounds(pos)){ stopDrawing(pos); } if(!isDrawing && hasMouseDown && isInsideDrawingBounds(pos)){ isDrawing = true; } if(isDrawing) { strokes.push([pos['x'], pos['y'], 0]); let d = strokes2D(strokes); strokeEl.setAttribute('d', d); } console.log([pos['x'], pos['y']], isDrawing); socket.send(JSON.stringify({ 'action': 'move', 'direction': [pos['x'], pos['y']], 'mouse': isDrawing, })); }; let stopDrawing = function(pos) { if(!isDrawing) { return; } isDrawing = false; //document.body.removeEventListener('mousemove', draw); if(strokes.length > 0){ // mark point as last of stroke strokes[strokes.length - 1][2] = 1; } socket.send(JSON.stringify({ 'action': 'up', 'direction': [pos['x'], pos['y']] })); } let penup = function(e) { if(!hasMouseDown) { return; } hasMouseDown = false; let pos = getCoordinates(e); stopDrawing(pos); }; let strokes2D = function(strokes) { // strokes to a d attribute for a path let d = ""; let last_stroke = undefined; let cmd = ""; for (let stroke of strokes) { if(!last_stroke) { d += `M${stroke[0]*svgWidth*10},${stroke[1]*svgHeight*10} `; cmd = 'M'; } else { if (last_stroke[2] == 1) { d += " m"; cmd = 'm'; } else if (cmd != 'l') { d+=' l '; cmd = 'l'; } let rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]]; d += `${rel_stroke[0]*svgWidth*10},${rel_stroke[1]*svgHeight*10} `; } last_stroke = stroke; } return d; } let startDrawing = function(e){ hasMouseDown = true; }; /*let reset = function() { strokes = []; strokeEl.setAttribute('d', ""); socket.send(JSON.stringify({ 'action': 'reset', })); }*/ let socket = new WebSocket(url); document.body.addEventListener('mousemove', draw); document.body.addEventListener('mouseup', penup); document.body.addEventListener('mousedown', startDrawing); socket.addEventListener('message', function(e){ let msg = JSON.parse(e.data); console.log('receive', msg); if(msg['action'] == 'submitted') { document.body.classList.add('submitted'); messageEl.innerHTML = msg['msg']; svgEl.removeEventListener('mousedown', startDrawing); } }); //resetEl.addEventListener('click', reset); submitEl.addEventListener('click', function(e){ if(!strokes.length){ alert('please draw before submitting'); return; } socket.send(JSON.stringify({ 'action': 'submit', 'd': strokeEl.getAttribute('d') })); document.body.removeEventListener('mousemove', draw); document.body.removeEventListener('mouseup', penup); document.body.removeEventListener('mousedown', startDrawing); }); </script> </body> </html>