1

I want to replay 1:1 the all click events. Unfortunately, it doesn't work for a dropdown. Whenever I select a value in a dropdown, it shows the wrong position (feel free to test it in my code snipped). I took the event handler from https://stackoverflow.com/a/61732450 and it worked like a charm for all my work, except here.

Left: What I see Right: What I want

enter image description here

Thank you very much

let touch_positions = [];
function process_touchevent(e) {
  if (
    e.type == "touchstart" ||
    e.type == "touchmove" ||
    e.type == "touchend" ||
    e.type == "touchcancel"
  ) {
    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 (
    e.type == "click" ||
    e.type == "mousedown" ||
    e.type == "mouseup" ||
    e.type == "mousemove" ||
    e.type == "mouseover" ||
    e.type == "mouseout" ||
    e.type == "mouseenter" ||
    e.type == "mouseleave"
  ) {
    x = e.clientX;
    y = e.clientY;
  }
  touch_positions.push({ x, y, type: e.type, date: new Date() });

  var elemDiv = document.createElement("div");
  elemDiv.style.cssText =
    "position:absolute;width:10px;height:10px;border-radius: 10px;opacity:0.3;z-index:3;background:red;";
  let position_text = "left:" + x + "px;top:" + y + "px;";
  elemDiv.style.cssText += position_text;
  document.body.appendChild(elemDiv);
}

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("click", process_touchevent, false);

function draw_all_touch_positions() {
  touch_positions.forEach(function (li) {
    var elemDiv = document.createElement("div");
    elemDiv.style.cssText =
      "position:absolute;width:10px;height:10px;border-radius: 10px;opacity:0.3;z-index:3;background:red;";
    let position_text = "left:" + li.x + "px;top:" + li.y + "px;";
    elemDiv.style.cssText += position_text;
    document.body.appendChild(elemDiv);
  });
  touch_positions = [];
}
<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>

<button onclick='draw_all_touch_positions()'>Draw</button>

2 Answers2

0

Using mousedown instead of click seems to work for the initial dropdown clicks, but not the <option>'s

let touch_positions = [];
function process_touchevent(e) {
  if (
    e.type == "touchstart" ||
    e.type == "touchmove" ||
    e.type == "touchend" ||
    e.type == "touchcancel"
  ) {
    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 (
    e.type == "click" ||
    e.type == "mousedown" ||
    e.type == "mouseup" ||
    e.type == "mousemove" ||
    e.type == "mouseover" ||
    e.type == "mouseout" ||
    e.type == "mouseenter" ||
    e.type == "mouseleave"
  ) {
    x = e.clientX;
    y = e.clientY;
  }
  touch_positions.push({ x, y, type: e.type, date: new Date() });

  var elemDiv = document.createElement("div");
  elemDiv.style.cssText =
    "position:absolute;width:10px;height:10px;border-radius: 10px;opacity:0.3;z-index:3;background:red;";
  let position_text = "left:" + x + "px;top:" + y + "px;";
  elemDiv.style.cssText += position_text;
  document.body.appendChild(elemDiv);
}

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(function (li) {
    var elemDiv = document.createElement("div");
    elemDiv.style.cssText =
      "position:absolute;width:10px;height:10px;border-radius: 10px;opacity:0.3;z-index:3;background:red;";
    let position_text = "left:" + li.x + "px;top:" + li.y + "px;";
    elemDiv.style.cssText += position_text;
    document.body.appendChild(elemDiv);
  });
  touch_positions = [];
}
<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>

<button onclick='draw_all_touch_positions()'>Draw</button>
Chris Barr
  • 29,851
  • 23
  • 95
  • 135
  • Yeah, unfortunately it does not solve the problem :( – MasterOfDesaster Aug 11 '23 at 07:23
  • True, but it at least is a little bit closer. This is a really strange problem for sure! I am just as surprised as you and I don't understand why this happens. I wonder if you could put a transparent div over the entire page that was there just to capture mouse x/y positions when a click event occurred? – Chris Barr Aug 11 '23 at 11:17
  • Wow, good idea! but doesn't work either :D I assume I have to execute plan B, which is implementing a dropdown myself using div's. :( – MasterOfDesaster Aug 11 '23 at 11:57
0

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!

Chris Barr
  • 29,851
  • 23
  • 95
  • 135
  • Hello Chris, thanks for the effort! But for a usability study it is important, to exactly have the clicked position instead of a guessed one. Some user might hold the mouse key pressed for some before releasing it. Or some might click the area outside the text instead of the text. I guess i will do plan B, implement my own select tag :( – MasterOfDesaster Aug 24 '23 at 17:08