I have two potential solution here.
A simple and obious solution to me seemed to be just track the mouse movement at all times, and just record it on a click event. Simple and even less code than that you had before!
However, <select>
's apparently are very strange in that they do not record any mouse events at all! You'll notice that the mouse move events stop being logged out when the menu is open and the mouse moves over them! The only exception is when you specify size="2"
or larger.
Very lame!
const mousePos = {x:0, y:0};
let touch_positions = [];
function process_touchevent(e) {
console.log(e.type, mousePos.x, mousePos.y)
touch_positions.push({ x: mousePos.x, y: mousePos.y, type: e.type, date: new Date() });
draw_touch_position(mousePos.x, mousePos.y);
}
window.addEventListener("touchstart", process_touchevent, false);
window.addEventListener("touchmove", process_touchevent, false);
window.addEventListener("touchcancel", process_touchevent, false);
window.addEventListener("touchend", process_touchevent, false);
window.addEventListener("mousedown", process_touchevent, false);
window.addEventListener("mousemove", (ev)=>{
mousePos.x = ev.clientX;
mousePos.y = ev.clientY;
console.log('move', mousePos.x, mousePos.y)
}, false);
function draw_all_touch_positions() {
touch_positions.forEach(li => {
draw_touch_position(li.x, li.y);
});
touch_positions = [];
}
function draw_touch_position(x, y){
var elemDiv = document.createElement("div");
elemDiv.classList.add("touch-point");
elemDiv.style.cssText = `left:${x}px;top:${y}px;`;
document.body.appendChild(elemDiv);
}
.touch-point {
position:absolute;
width:10px;
height:10px;
border-radius: 10px;
opacity:0.3;
z-index:3;
background:red;
transform: translate(-50%, -50%); /*Make the point appear in the center of the x/y coordinates*/
pointer-events:none; /*don't allow these to become click targets, clicks will pass through now*/
}
<div id="touch-capture"></div>
<select name="cars" id="cars" size="1">
<option value="volvo">Volvo</option>
<option value="saab" selected="">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
<select name="cars" id="cars" size="2">
<option value="volvo">Volvo</option>
<option value="saab" selected="">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
<input type="text">
<input type="checkbox">
<button type="button" onclick='draw_all_touch_positions()'>Draw</button>
In this next one, since it seems clicks on <option>
's cannot be captured, we can at least try an estimate the option position by getting the selected option index and multiplying that by what we expect the height of the option to be. Option sizes cannot be styled by CSS and might vary between different browsers and OS's so this might work if you can know these sizes.
let touch_positions = [];
function process_touchevent(e) {
if (/^touch(start|move|end|cancel)$/i.test(e.type)) {
var evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
var touch = evt.touches[0] || evt.changedTouches[0];
x = touch.pageX;
y = touch.pageY;
} else if (/^click|mouse(down|up|move|over|out|enter|leave)$/i.test(e.type)) {
x = e.clientX;
y = e.clientY;
}
//consolelog(document.elementFromPoint(x,y));
//Cannot get positions for `<option>` elements, so we must estimate!
if(e.target.tagName === "OPTION"){
//console.log(e.target.getBoundingClientRect())
//console.log(getComputedStyles(e.target))
const selectedIndex = e.target.parentElement.selectedIndex;
const optLineHeight = 25; //this might vary in different browsers and OS's!!!!
const parentRect = e.target.parentElement.getBoundingClientRect();
const optMenucenterX = parentRect.x + (parentRect.width / 2);
const optMenuStartY = parentRect.y + parentRect.height;
x = optMenucenterX;
y = optMenuStartY + (optLineHeight * selectedIndex) + 10;
}
touch_positions.push({ x, y, type: e.type, date: new Date() });
draw_touch_position(x, y);
}
window.addEventListener("touchstart", process_touchevent, false);
window.addEventListener("touchmove", process_touchevent, false);
window.addEventListener("touchcancel", process_touchevent, false);
window.addEventListener("touchend", process_touchevent, false);
window.addEventListener("mousedown", process_touchevent, false);
function draw_all_touch_positions() {
touch_positions.forEach(li => {
draw_touch_position(li.x, li.y);
});
touch_positions = [];
}
function draw_touch_position(x, y){
var elemDiv = document.createElement("div");
elemDiv.classList.add("touch-point");
elemDiv.style.cssText = `left:${x}px;top:${y}px;`;
document.body.appendChild(elemDiv);
}
.touch-point {
position:absolute;
width:10px;
height:10px;
border-radius: 10px;
opacity:0.3;
z-index:3;
background:red;
transform: translate(-50%, -50%); /*Make the point appear in the center of the x/y coordinates*/
pointer-events:none; /*don't allow these to become click targets, clicks will pass through now*/
}
<div id="touch-capture"></div>
<select name="cars" id="cars">
<option value="volvo">Volvo</option>
<option value="saab" selected="">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
<input type="text">
<input type="checkbox">
<button type="button" onclick='draw_all_touch_positions()'>Draw</button>
It's for sure not perfect but it's a whole lot closer to your goal. Hopefully this is the only exception that has to be made!