What is the equivalent of dragStart event.datatransfer.setData for touchStart? - javascript

In react I have an element that I want to drag and drop. Right now I have a dragStart and dragEnd that uses ev.dataTransfer.setData(data) to drag and drop my element to a different box.
However, I also want to be able to do this on a touch screen. When I use a touchscreen, the dragStart doesn't do anything. I figured out instead that I should use TouchStart and TouchEnd for the touchscreen. However, there is no dataTransfer event with touchStart. I'm wondering what the equivalent would be?
If there is no equivalent, what would be a good way for me to drag and drop my elements on touchscreen?
Thanks

Reactjs is abit alien to me, but generally in vanilla, dataTransfer for touch can be done by storing your data into object/variable. Basically that's all dataTransfer do while you drag an element.
To do this, ontouchstart event, store the data that you want to carry like you did with dataTransfer.setData(), then simulate the dragging of an element by duplicating it.
touchstart(e, item, index, arr) {
this.state.touch['item'] = item
this.state.touch['index'] = index
this.state.touch['arr'] = arr
let image = document.createElement("img"); // Create a new element
image.setAttribute("id", "image-float");
// get the image from the stored reference
image.src = 'https://cdn.quasar.dev/img/avatar' + item + '.jpg';
image.width = 100
image.height = 100
// position the image to the touch, can be improve to detect the position of touch inside the image
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
image.style.opacity = 0.5;
document.getElementById('app').appendChild(image);
}
then in ontouchmove event, you can have the duplicated element to follow cursor, to simulate the dragging effect. while on touchmove event, we can detect either our cursor is inside the dropzone element, like you would normally have as onDrop event for DnD. this is the manual part that DnD has helped to smoothen the experience, but for touch, we need to do the detection by ourself.
touchmove(e) {
let image = document.getElementById('image-float')
// this will give us the dragging feeling of the element while actually it's a different element
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
let topDrop = this.getDropzone(e, left, top, 'top')
let botDrop = this.getDropzone(e, left, top, 'bottom')
if (!topDrop && !botDrop) this.state.touch.drop = null
if (topDrop) this.state.touch.drop = 'top'
if (botDrop) this.state.touch.drop = 'bottom'
}
getDropzone(e, left, top, id) {
let rect1 = document.getElementById(id).getBoundingClientRect();
// to detect the overlap of mouse into the dropzone, as alternative of mouseover
var overlap = !(rect1.right < left ||
rect1.left > left ||
rect1.bottom < top ||
rect1.top > top)
return overlap
}
finally, in our ontouchend event, we do the logic as we have for our dataTransfer.getData(). Because we already detect if our cursor is within the dropzone or not, so we can perform the logic accordingly
touchend() {
let image = document.getElementById('image-float')
image.remove()
let item = this.state.touch.item
let index = this.state.touch.index
let arr = this.state[this.state.touch.arr]
let drop = this.state[this.state.touch.drop]
/* console.log(this.state.touch.arr) */
if (this.state.touch.drop != null) {
if (drop != arr) {
drop.push(item)
arr.splice(index, 1)
}
return this.setState({
state: {
drop: this.state[drop],
arr: this.state[arr]
}
})
}
}
Below, is my attempt on having this delivered in Reactjs
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
top: [
'2',
'3',
'4',
],
bottom: [],
touch: {}
}
}
dragstart(e, item, index, arr) {
e.dataTransfer.dropEffect = 'move'
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData('item', item)
e.dataTransfer.setData('index', index)
e.dataTransfer.setData('arr', arr)
}
drop(e, drop) {
let item = e.dataTransfer.getData('item')
let index = e.dataTransfer.getData('index')
let arr = e.dataTransfer.getData('arr')
if (drop != item.arr) {
this.state[drop].push(item)
this.state[arr].splice(index, 1)
}
return this.setState({
state: {
drop: this.state[drop],
arr: this.state[arr]
}
})
}
touchstart(e, item, index, arr) {
this.state.touch['item'] = item
this.state.touch['index'] = index
this.state.touch['arr'] = arr
let image = document.createElement("img"); // Create a new element
image.setAttribute("id", "image-float");
// get the image from the stored reference
image.src = 'https://cdn.quasar.dev/img/avatar' + item + '.jpg';
image.width = 100
image.height = 100
// position the image to the touch, can be improve to detect the position of touch inside the image
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
image.style.opacity = 0.5;
document.getElementById('app').appendChild(image);
}
touchmove(e) {
let image = document.getElementById('image-float')
// this will give us the dragging feeling of the element while actually it's a different element
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
let topDrop = this.getDropzone(e, left, top, 'top')
let botDrop = this.getDropzone(e, left, top, 'bottom')
if (!topDrop && !botDrop) this.state.touch.drop = null
if (topDrop) this.state.touch.drop = 'top'
if (botDrop) this.state.touch.drop = 'bottom'
}
getDropzone(e, left, top, id) {
let rect1 = document.getElementById(id).getBoundingClientRect();
// to detect the overlap of mouse into the dropzone, as alternative of mouseover
var overlap = !(rect1.right < left ||
rect1.left > left ||
rect1.bottom < top ||
rect1.top > top)
return overlap
}
touchend() {
let image = document.getElementById('image-float')
image.remove()
let item = this.state.touch.item
let index = this.state.touch.index
let arr = this.state[this.state.touch.arr]
let drop = this.state[this.state.touch.drop]
/* console.log(this.state.touch.arr) */
if (this.state.touch.drop != null) {
if (drop != arr) {
drop.push(item)
arr.splice(index, 1)
}
return this.setState({
state: {
drop: this.state[drop],
arr: this.state[arr]
}
})
}
}
render() {
return ( <
div className = "container" >
<
div id = "top"
className = "dropzone"
onDrop = {
(event) => this.drop(event, 'top')
}
onDragOver = {
(e) => {
e.preventDefault()
}
}
onDragEnter = {
(e) => {
e.preventDefault()
}
} > {
this.state.top.map((item, i) => {
return ( < img src = {
'https://cdn.quasar.dev/img/avatar' + item + '.jpg'
}
width = "100"
height = "100"
key = {
i
}
onDragStart = {
(event) => this.dragstart(event, item, i, 'top')
}
onTouchStart = {
(event) => this.touchstart(event, item, i, 'top')
}
onTouchMove = {
(event) => this.touchmove(event)
}
onTouchEnd = {
(event) => this.touchend()
}
draggable = 'true' /
>
)
})
} < /div>
<
div id = "bottom"
className = "dropzone"
onDrop = {
(event) => this.drop(event, 'bottom')
}
onDragOver = {
(e) => {
e.preventDefault()
}
}
onDragEnter = {
(e) => {
e.preventDefault()
}
} > {
this.state.bottom.map((item, i) => {
return ( < img src = {
'https://cdn.quasar.dev/img/avatar' + item + '.jpg'
}
width = "100"
height = "100"
key = {
i
}
onDragStart = {
(event) => this.dragstart(event, item, i, 'bottom')
}
onDragStart = {
(event) => this.dragstart(event, item, i, 'bottom')
}
onTouchStart = {
(event) => this.touchstart(event, item, i, 'bottom')
}
onTouchMove = {
(event) => this.touchmove(event)
}
onTouchEnd = {
(event) => this.touchend()
}
draggable = 'true' /
>
)
})
} < /div> </div >
)
}
}
ReactDOM.render( < TodoApp / > , document.getElementById("app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
button {
color: #4fc08d;
}
button {
background: none;
border: solid 1px;
border-radius: 2em;
font: inherit;
padding: 0.75em 2em;
}
.dropzone {
display: flex;
height: fit-content;
min-width: 50px;
min-height: 50px;
background: #2D2D2D;
margin: 10px;
padding: 10px;
}
.dropzone>* {
margin: 0 5px;
}
.container {
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

Prevent block from touching the containers borders

I have this little block that I move around using javascript code. It works all good except if I keep moving it, it can easily get out of the box where it is supposed to be.
Can I prevent this somehow? So no matter how far I want to move it, it will stay stuck inside of the container/box ?
Here's my snippet code:
/// store key codes and currently pressed ones
var keys = {};
keys.UP = 38;
keys.LEFT = 37;
keys.RIGHT = 39;
keys.DOWN = 40;
/// store reference to character's position and element
var character = {
x: 100,
y: 100,
speedMultiplier: 2,
element: document.getElementById("character")
};
var is_colliding = function(div1, div2) {
var d1_height = div1.offsetHeight;
var d1_width = div1.offsetWidth;
var d1_distance_from_top = div1.offsetTop + d1_height;
var d1_distance_from_left = div1.offsetLeft + d1_width;
var d2_height = div2.offsetHeight;
var d2_width = div2.offsetWidth;
var d2_distance_from_top = div2.offsetTop + d2_height;
var d2_distance_from_left = div2.offsetLeft + d2_width;
var not_colliding =
d1_distance_from_top <= div2.offsetTop ||
div1.offsetTop >= d2_distance_from_top ||
d1_distance_from_left <= div2.offsetTop ||
div1.offsetLeft >= d2_distance_from_left;
return !not_colliding;
};
/// key detection (better to use addEventListener, but this will do)
document.body.onkeyup =
document.body.onkeydown = function(e){
if (e.preventDefault) {
e.preventDefault();
}
else {
e.returnValue = false;
}
var kc = e.keyCode || e.which;
keys[kc] = e.type == 'keydown';
};
/// character movement update
var moveCharacter = function(dx, dy){
character.x += (dx||0) * character.speedMultiplier;
character.y += (dy||0) * character.speedMultiplier;
character.element.style.left = character.x + 'px';
character.element.style.top = character.y + 'px';
};
/// character control
var detectCharacterMovement = function(){
if ( keys[keys.LEFT] ) {
moveCharacter(-5, 0);
}
if ( keys[keys.RIGHT] ) {
moveCharacter(5, 0);
}
if ( keys[keys.UP] ) {
moveCharacter(0, -5);
}
if ( keys[keys.DOWN] ) {
moveCharacter(0, 5);
}
};
/// update current position on screen
moveCharacter();
/// game loop
setInterval(function(){
detectCharacterMovement();
}, 1000/24);
body{
display: flex;
justify-content: center;
align-items: center;
}
#character {
position: absolute;
width: 42px;
height: 42px;
background: red;
z-index:99;
}
#container{
width: 400px;
height: 400px;
background: transparent;
border:5px solid rgb(0, 0, 0);
position: relative;
overflow: hidden;
}
<div id="container">
<div id="character"></div>
</div>
PS: You can move the box using keyboard arrows.
Get the container width and height into variable and set a condition on your move
var moveCharacter = function(dx, dy){
let div_width = document.getElementById('container').clientWidth;
let div_height = document.getElementById('container').clientHeight;
if((div_width - character.x) < 50 ){ // 50 = width of character and padding
character.x = div_width - 50;
}
if(character.x < 10){ // Padding
character.x = 11;
}
if((div_height - character.y) < 50 ){
character.y = div_height - 50;
}
if(character.y < 10){
character.y = 11;
}

How to get postition highlighted text in textarea (coords) angular?

I want to get something like this, in textarea. I cant change textarea to contentaditable div. How do I get the position of the selected text? I need this to show this pop-up from above
You should be able to easily convert this into angular code.
This is the basics of what you need to do, its not thoroughly tested but it bare minimum works.
const textarea = document.getElementById('text')
const result = document.getElementById('selected')
const tooltip = document.getElementById('tooltip')
const cols = document.getElementById('text').cols;
const width = document.getElementById('text').clientWidth;
const height = window.getComputedStyle(textarea).lineHeight;
textarea.onclick = function getSelection() {
const pos = {
top: textarea.offsetTop,
left: textarea.offsetLeft,
};
result.textContent = `${textarea.selectionStart}, ${textarea.selectionEnd}`;
let selection
if (textarea.selectionStart) {
selection = textarea.selectionStart;
} else if (document.selection) {
textarea.focus();
const r = document.selection.createRange();
if (r == null) {
selection = 0;
}
let re = textarea.createTextRange();
let rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
selection = rc.text.length;
} else {
selection = 0
}
const row = Math.floor((selection - 1) / cols);
const col = (selection - (row * cols));
const x = Math.floor((col * (width / cols)));
const y = (parseInt(height) * row);
tooltip.innerHTML = "<b>row: " + row + "<br>columns" + col + "<br>width: " + width + "</b>";
tooltip.style.top = `${pos.top+y}px`;
tooltip.style.left = `${pos.left+x+10}px`;
}
textarea {
height: 80px;
line-height: 12px;
overflow-y: scroll;
display: block;
}
#tooltip {
position: absolute;
background:red;
color: white;
}
<textarea id="text">Lopsum</textarea>
<span id="tooltip"></span>
<span id="selected"></span>
The only way I imagine is create a duplicate of the text area (very similar to this SO, (the Owen Kelvin's response) about highligth words in a text area
As we only need the position, we can split the "texarea" and the "background". Futhermore, using the Yurzui response in this another SO we can control when resize the textarea
We can imagine an .html like
<div class="container">
<div
#backdrop
class="backdrop"
[style.width.px]="textWidth"
[style.height.px]="textHeight"
>
<div
class="highlights"
[innerHTML]="sanitizer.bypassSecurityTrustHtml(highlightedText)"
></div>
</div>
</div>
<textarea
#textarea
spellcheck="false"
(resize)="rect = null;"
(select)="applyHighlights(textarea.value)"
(mousedown)="mouseDown()"
(blur)="rect = null"
[ngModel]="textValue"
(ngModelChange)="textValue = $event; applyHighlights(textarea.value)"
(scroll)="handleScroll(); applyHighlights(textarea.value)"
></textarea>
<div
class="tooltip"
*ngIf="rect"
[style.top.px]="rect.y"
[style.left.px]="rect.x"
></div>
See that the "text" is hidden because we has a div container like
.container {
overflow:hidden;
width:0;
height:0;
}
And we make that the dimensions of "text" are condition by the two variables "textWidth" and "textHeight"
The code is
ngOnInit() {
this.resize();
}
resize() {
const event = {
width: this.$textarea.nativeElement.getBoundingClientRect().width,
height: this.$textarea.nativeElement.getBoundingClientRect().height,
};
this.textWidth = event.width;
this.textHeight = event.height;
}
mouseDown() {
setTimeout(() => {
const start = this.$textarea.nativeElement.selectionStart;
const end = this.$textarea.nativeElement.selectionEnd;
if (start == end) this.rect = null;
});
}
applyHighlights(text: string) {
if (text) {
let start = this.$textarea.nativeElement.selectionStart;
let end = this.$textarea.nativeElement.selectionEnd;
if (start == end) this.highlightedText = text;
else {
const selected = text.substr(start, end - start);
this.toolTipText=this.getTooltipText(selected)
this.highlightedText =
text.substr(0, start) +
"<span id='mark'>" +
selected +
'</span>' +
text.substr(end);
this.resize();
setTimeout(() => {
const recArea = this.$textarea.nativeElement.getBoundingClientRect();
const recText = this.$backdrop.nativeElement.getBoundingClientRect();
const rect = document.getElementById('mark').getBoundingClientRect();
rect.y += window.scrollY;
rect.x+=rect.width/2
this.rect = rect.y - window.scrollY < recArea.y ? null : rect;
});
}
}
}
handleScroll() {
var scrollTop = this.$textarea.nativeElement.scrollTop;
this.$backdrop.nativeElement.scrollTop = scrollTop;
var scrollLeft = this.$textarea.nativeElement.scrollLeft;
this.$backdrop.nativeElement.scrollLeft = scrollLeft;
}
And in the stackblitz I put in a custom form component (the reason is that Owen make this work for me

HTML Drag and Drop on touchscreen

I am working on a Task which involves dragging of images and checking where it is dropping and performing action if it is dropped on right location. While it is working absolutely fine on anything that has a mouse it does not work on a touchscreen. How can achieve this goal on a touchscreen. using Vuejs 2 or vanilla javascript
Drag Item
<v-row v-for="(item, iterator) in Activity.drag_items" :key="item.class" :class="[item.class, item.status]" class="drag-item">
<v-img
draggable
#dragstart='startDrag($event, item, iterator)'
:src="require(`#/assets/img/activities/activity_2/${item.item_img}`)"
contain
:class="item.status"
></v-img>
</v-row>
Drop Item
<a #drop='onDrop($event, Activity)' #dragover.prevent #dragenter.prevent></a>
On Drag Function
startDrag(evt, item, index){
evt.dataTransfer.dropEffect = 'move';
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('item', JSON.stringify(item));
evt.dataTransfer.setData('index', index);
}
On Drop Function
onDrop(evt, galaxy_location) {}
As for now, there is no dataTransfer object for touch event. One way to do this is to have methods that copy the value or data and mutate it base on the touch event. In my example I have three methods to simulate the drag and drop with touch
On touch start, I stored the reference that is needed into object, this is similar to dataTransfer.setData(), but added work here is to simulate the feeling of drag and drop by removing item on touchstart and duplicate a new element to follow your touch event
touchstartDrag(e, item, arr) {
// go through origin array
arr.forEach((el, i) => {
if (el == item) {
// store it as reference
this.touchDragItem = {
item: item,
index: i,
arr: arr
}
// remove item in the array, or you can change opacity
arr.splice(i, 1)
}
})
let image = document.createElement("img"); // Create a new element
image.setAttribute("id", "image-float");
// get the image from the stored reference
image.src = `https://cdn.quasar.dev/img/avatar${this.touchDragItem.item}.jpg`;
image.width = 100
image.height = 100
// position the image to the touch, can be improve to detect the position of touch inside the image
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
document.getElementById('app').appendChild(image);
},
On touchmove, this is purely to simulate the dragging feeling that you get from dnd, get the element created in touchstart and make it follow your touchmove
touchmoveDrag(e) {
// on touch move or dragging, we get the newly created image element
let image = document.getElementById('image-float')
// this will give us the dragging feeling of the element while actually it's a different element
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
this.touchX = e.touches[0].pageX
this.touchY = e.touches[0].pageY
},
On touch end where you define your drop function. Because there is no drop event, you have to manually detect your touch according to the dropzone, if it's outside the dropzone, define your logic, if it's within, execute it accordingly as you define dataTransfer.getData()
touchendDrag(e) {
// remove the image on touch end
let image = document.getElementById('image-float')
image.remove()
// get the dropzone of top and bottom
let rect1 = document.getElementById('top').getBoundingClientRect();
let rect2 = document.getElementById('bottom').getBoundingClientRect()
// to detect the overlap of mouse into the dropzone, as alternative of mouseover
var overlapTop = !(rect1.right < this.touchX ||
rect1.left > this.touchX ||
rect1.bottom < this.touchY ||
rect1.top > this.touchY)
// to detect the overlap of mouse into the dropzone bottom
var overlapBottom = !(rect2.right < this.touchX ||
rect2.left > this.touchX ||
rect2.bottom < this.touchY ||
rect2.top > this.touchY)
// get the stored reference
let ex = this.touchDragItem
// if on touchend the touch is not inside any dropzone, just restore back to the original array
if (!overlapTop && !overlapBottom) {
ex.arr.splice(ex.index, 0, ex.item)
} else {
if (overlapTop) {
if (this.top == ex.arr) {
ex.arr.splice(ex.index, 0, ex.item)
}
if (this.top != ex.arr) {
this.top.push(ex.item)
}
}
if (overlapBottom) {
if (this.bottom == ex.arr) {
ex.arr.splice(ex.index, 0, ex.item)
}
if (this.bottom != ex.arr) {
this.bottom.push(ex.item)
}
}
}
this.touchDragItem = null
},
Overall this is a naive approach to simulate dnd API for touch event, there are plenty of vue.js drag and drop library for you to go, depends on your use case such as sortable.js . But if you want to implement your own touch drag, this is somewhere you can start off
Here is the full working example
new Vue({
el: "#app",
data: {
touchDragItem: null,
touchX: null,
touchY: null,
top: ['1', '2', '3'],
bottom: [],
},
methods: {
dragmouse(e, item) {
e.dataTransfer.dropEffect = 'move'
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData('item', item)
},
onDrop(e, pos) {
let arr = pos == 'top' ? 'bottom' : 'top'
let item = e.dataTransfer.getData('item')
this[arr].forEach((el, i) => {
if (el == item) {
this[arr].splice(i, 1)
this[pos].push(el)
}
})
},
touchstartDrag(e, item, arr) {
// go through origin array
arr.forEach((el, i) => {
if (el == item) {
// store it as reference
this.touchDragItem = {
item: item,
index: i,
arr: arr
}
// remove item in the array, or you can change opacity
arr.splice(i, 1)
}
})
let image = document.createElement("img"); // Create a new element
image.setAttribute("id", "image-float");
// get the image from the stored reference
image.src = `https://cdn.quasar.dev/img/avatar${this.touchDragItem.item}.jpg`;
image.width = 100
image.height = 100
// position the image to the touch, can be improve to detect the position of touch inside the image
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
document.getElementById('app').appendChild(image);
},
touchmoveDrag(e) {
// on touch move or dragging, we get the newly created image element
let image = document.getElementById('image-float')
// this will give us the dragging feeling of the element while actually it's a different element
let left = e.touches[0].pageX;
let top = e.touches[0].pageY;
image.style.position = 'absolute'
image.style.left = left + 'px';
image.style.top = top + 'px';
this.touchX = e.touches[0].pageX
this.touchY = e.touches[0].pageY
},
touchendDrag(e) {
// remove the image on touch end
let image = document.getElementById('image-float')
image.remove()
// get the dropzone of top and bottom
let rect1 = document.getElementById('top').getBoundingClientRect();
let rect2 = document.getElementById('bottom').getBoundingClientRect()
// to detect the overlap of mouse into the dropzone, as alternative of mouseover
var overlapTop = !(rect1.right < this.touchX ||
rect1.left > this.touchX ||
rect1.bottom < this.touchY ||
rect1.top > this.touchY)
// to detect the overlap of mouse into the dropzone bottom
var overlapBottom = !(rect2.right < this.touchX ||
rect2.left > this.touchX ||
rect2.bottom < this.touchY ||
rect2.top > this.touchY)
// get the stored reference
let ex = this.touchDragItem
// if on touchend the touch is not inside any dropzone, just restore back to the original array
if (!overlapTop && !overlapBottom) {
ex.arr.splice(ex.index, 0, ex.item)
} else {
if (overlapTop) {
if (this.top == ex.arr) {
ex.arr.splice(ex.index, 0, ex.item)
}
if (this.top != ex.arr) {
this.top.push(ex.item)
}
}
if (overlapBottom) {
if (this.bottom == ex.arr) {
ex.arr.splice(ex.index, 0, ex.item)
}
if (this.bottom != ex.arr) {
this.bottom.push(ex.item)
}
}
}
this.touchDragItem = null
},
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
button {
color: #4fc08d;
}
button {
background: none;
border: solid 1px;
border-radius: 2em;
font: inherit;
padding: 0.75em 2em;
}
.dropzone {
display: flex;
height: fit-content;
min-width: 50px;
min-height: 50px;
background: #2D2D2D;
margin: 10px;
padding: 10px;
}
.dropzone>* {
margin: 0 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="top" class="dropzone" #drop="onDrop($event,'top')" #dragenter.prevent #dragover.prevent>
<img ref="image" draggable #dragstart="dragmouse($event,item)" #touchstart.prevent="touchstartDrag($event,item,top)" #touchmove="touchmoveDrag" #touchend="touchendDrag" :src="`https://cdn.quasar.dev/img/avatar${item}.jpg`" width="100" height="100" v-for="item in top"
:key="item" />
</div>
<div id="bottom" class="dropzone" #drop="onDrop($event,'bottom')" #dragenter.prevent #dragover.prevent>
<img ref="image" draggable #dragstart="dragmouse($event,item)" #touchstart.prevent="touchstartDrag($event,item,bottom)" #touchmove="touchmoveDrag" #touchend="touchendDrag" :src="`https://cdn.quasar.dev/img/avatar${item}.jpg`" width="100" height="100"
v-for="item in bottom" :key="item" />
</div>
</div>

tooltip in pure JS

I am trying this code but i get: document.getElementsByName(...).style is undefined
I have also a problem with the delegation, i think. Any help?
<html>
<head>
<style type="text/css">
#toolTip {
position:relative;
width:200px;
margin-top:-90px;
}
#toolTip p {
padding:10px;
background-color:#f9f9f9;
border:solid 1px #a0c7ff;
-moz-border-radius:5px;-ie-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;border-radius:5px;
}
#tailShadow {
position:absolute;
bottom:-8px;
left:28px;
width:0;height:0;
border:solid 2px #fff;
box-shadow:0 0 10px 1px #555;
}
#tail1 {
position:absolute;
bottom:-20px;
left:20px;
width:0;height:0;
border-color:#a0c7ff transparent transparent transparent;
border-width:10px;
border-style:solid;
}
#tail2 {
position:absolute;
bottom:-18px;
left:20px;
width:0;height:0;
border-color:#f9f9f9 transparent transparent transparent;
border-width:10px;
border-style:solid;
}
</style>
<script type='text/javascript'>
function load () {
var elements = document.getElementsByName('toolTip');
for(var i=0; i<elements.length; i++) {
document.getElementsByName(elements[i]).style.visibility = 'hidden';
}
}
</script>
</head>
<body onload="load()">
<br><br><br><br><br><br><br><br><br><br><br><br>
<a class="hd"
onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
<div id="toolTip" name="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
<br><br><br>
<a class="hd"
onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
<div id="toolTip" name="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
</body>
</html>
demo
Try changing the id toolTip to a class:
<div class="toolTip">...</div>
And change your JS to use the display style-thing, rather than visibility, nd the onmouseover's are best dealt with using JS event delegation:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
mouseOver = function(e)
{//handler for mouseover
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;//gets the next element in DOM (ie the tooltip)
//check if mouse is over a relevant element:
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/))
{//nope? stop here, then
return e;
}
targetToolTip.style.display = 'block';//make visible
for (i=0;i<tooltips.length;i++)
{//closures are neat --> you have a reference to all tooltip elements from load scope
if (tooltips[i] !== targetToolTip)
{//not the one you need to see
tooltips[i].style.display = 'none';
}
}
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
//add listener:
if (document.body.addEventListener)
{//IE > 9, chrome, safari, FF...
document.body.addEventListener('mouseover',mouseOver,false);
}
else
{//IE8
document.body.attachEvent('onmouseover',mouseOver);
}
}
Google JavaScript event delegation and closures if this code isn't clear, but that's just how I would tackle this kind of thing. IMO, it's fairly efficient (you could use the closure scope to keep track of the tooltip that's currently visible and not loop through all of them, too, that would be even better:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
currentToolTip,//<-- reference currently visible
mouseOver = function(e)
{
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
{//add check for currently visible TT, if so, no further action required
return e;
}
if (currentToolTip !== undefined)
{
currentToolTip.style.display = 'none';//hide currently visible
}
targetToolTip.style.display = 'block';//make new visible
currentToolTip = targetToolTip;//keep reference for next event
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
if (document.body.addEventListener)
{
document.body.addEventListener('mouseover',mouseOver,false);
}
else
{
document.body.attachEvent('onmouseover',mouseOver);
}
}
And you're there.
Edit:
To hide the tooltip on mouseout, you can either add a second listener directly:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
currentToolTip,//<-- reference currently visible
mouseOver = function(e)
{
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
{//add check for currently visible TT, if so, no further action required
return e;
}
if (currentToolTip !== undefined)
{
currentToolTip.style.display = 'none';//hide currently visible
}
targetToolTip.style.display = 'block';//make new visible
currentToolTip = targetToolTip;//keep reference for next event
},
mouseOut = function(e)
{
e = e || window.event;
var movedTo = document.elementFromPoint(e.clientX,e.clientY);//check where the cursor is NOW
if (movedTo === curentToolTip || currentToolTip === undefined)
{//if cursor moved to tooltip, don't hide it, if nothing is visible, stop
return e;
}
currentTooltip.style.display = 'none';
currentTooltip = undefined;//no currentToolTip anymore
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
if (document.body.addEventListener)
{
document.body.addEventListener('mouseover',mouseOver,false);
document.body.addEventListener('mouseout',mouseOut,false);
}
else
{
document.body.attachEvent('onmouseover',mouseOver);
document.body.attachEvent('onmouseout',mouseOut);
}
}
Note, this is completely untested. I'm not entirely sure if IE < 9 supports elementFromPoint (gets the DOM element that is rendered at certain coordinates), or even if the IE event object has the clientX and clientY properties, but I figure a quick google will tell you more, including how to get the coordinates and the element that is to be found under the cursor in old, crummy, ghastly IE8, but this should help you on your way. Of course, if you don't want the contents of the tooltip to be selectable, just change the mouseOut function to:
mouseOut = function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (currentToolTip)
{
currentToolTip.style.diplay = 'none';
currentToolTip = undefined;
}
};
No need to check if the mouseout was on the correct element, just check if there is a current tooltip, and hide it.
Try using classes to mark the tooltips:
<div id="toolTip1" class="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
And JQuery to toggle the visibility using the class as the selector:
$('.toolTip').attr('visibility', 'hidden')
Definitely clean up the non-unique Id's - this will cause you no end of troubles otherwise
Your problem is likely because you're using the same id for both the tooltips. This is invalid; an id should be unique -- only one element in a given page should have a specific ID.
If you need a shared identifier for multiple objects, use a class instead.
I built a tooltip with a border in pure js that doesn't use hover.
html
<div id="infoId" class='info' style="font-variant:small-caps;text-align:center;padding-top:10px;">
<span id="innerspanid">
</span>
</div>
</div>
<input id="startbtn" class="getstartedbtn" type="button" value="Start >" />
</div>
js
function getTextWidth(text, font) {
// re-use canvas object for better performance
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
const context = canvas.getContext("2d");
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}
function getCssStyle(element, prop) {
return window.getComputedStyle(element, null).getPropertyValue(prop);
}
function getCanvasFontSize(el = document.body) {
const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
const fontSize = getCssStyle(el, 'font-size') || '16px';
const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
return `${fontWeight} ${fontSize} ${fontFamily}`;
}
let arrowDimensionWidth = 20;
let arrowDimensionHeight = 20;
let tooltipTextHorizontalMargin = 50;
function openTooltip(text) {
let innerSpan = document.getElementById("innerspanid");
innerSpan.innerHTML = text;
let computedW = getTextWidth(text, getCanvasFontSize(innerSpan)) + tooltipTextHorizontalMargin;
let pointer = document.getElementById('pointer')
pointer.style.right = (((computedW / 2) - (arrowDimensionWidth / 2)) - (0)) + 'px';
let elem = document.getElementById('tooltipHost').parentNode.querySelector('div.info_container');
elem.style.left = ((tooltipHost.getBoundingClientRect().width - computedW) / 2) + "px";
elem.style.width = computedW + "px";
elem.style.display = 'block';
}
function buildTooltip() {
let elements = document.querySelectorAll('div.tooltip');
// Create a canvas element where the triangle will be drawn
let canvas = document.createElement('canvas');
canvas.width = arrowDimensionWidth; // arrow width
canvas.height = arrowDimensionHeight; // arrow height
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'darkred'; // Border color
ctx.fillStyle = 'white'; // background color
ctx.lineWidth = 1;
ctx.translate(-0.5, -0.5); // Move half pixel to make sharp lines
ctx.beginPath();
ctx.moveTo(1, canvas.height); // lower left corner
ctx.lineTo((canvas.width / 2), 1); // upper right corner
ctx.lineTo(canvas.width, canvas.height); // lower right corner
ctx.fill(); // fill the background
ctx.stroke(); // stroke it with border
ctx.fillRect(0, canvas.height - 0.5, canvas.width - 1, canvas.height + 2); //fix bottom row
// Create a div element where the triangle will be set as background
pointer = document.createElement('div');
pointer.id = "pointer"
pointer.style.width = canvas.width + 'px';
pointer.style.height = canvas.height + 'px';
pointer.innerHTML = ' ' // non breaking space
pointer.style.backgroundImage = 'url(' + canvas.toDataURL() + ')';
pointer.style.position = 'absolute';
pointer.style.top = '2px';
pointer.style.zIndex = '1'; // place it over the other elements
let idx;
let len;
for (idx = 0, len = elements.length; idx < len; ++idx) {
let elem = elements[idx];
let text = elem.querySelector('div.info');
let info = document.createElement('div');
text.parentNode.replaceChild(info, text);
info.className = 'info_container';
info.appendChild(pointer.cloneNode());
info.appendChild(text);
}
}
window.addEventListener('load', buildTooltip);
window.addEventListener('load', wireup);
function wireup() {
document.getElementById('startbtn').addEventListener('click', function (evt1) {
openTooltip("bad email no # sign");
return false;
});
}
css
div.tooltip {
position: relative;
display: inline-block;
}
div.tooltip > div.info {
display: none;
}
div.tooltip div.info_container {
position: absolute;
left: 0px;
width: 100px;
height: 70px;
display: none;
}
div.tooltip div.info {
position: absolute;
left: 0px;
text-align: left;
background-color: white;
font-size: 18px;
left: 1px;
right: 1px;
top: 20px;
bottom: 1px;
color: #000;
padding: 5px;
overflow: auto;
border: 1px solid darkred;
border-radius: 5px;
}

How to distinguish mouse "click" and "drag"

I use jQuery.click to handle the mouse click event on Raphael graph, meanwhile, I need to handle mouse drag event, mouse drag consists of mousedown, mouseupand mousemove in Raphael.
It is difficult to distinguish click and drag because click also contain mousedown & mouseup, How can I distinguish mouse "click" & mouse "drag" then in Javascript?
I think the difference is that there is a mousemove between mousedown and mouseup in a drag, but not in a click.
You can do something like this:
const element = document.createElement('div')
element.innerHTML = 'test'
document.body.appendChild(element)
let moved
let downListener = () => {
moved = false
}
element.addEventListener('mousedown', downListener)
let moveListener = () => {
moved = true
}
element.addEventListener('mousemove', moveListener)
let upListener = () => {
if (moved) {
console.log('moved')
} else {
console.log('not moved')
}
}
element.addEventListener('mouseup', upListener)
// release memory
element.removeEventListener('mousedown', downListener)
element.removeEventListener('mousemove', moveListener)
element.removeEventListener('mouseup', upListener)
All these solutions either break on tiny mouse movements, or are overcomplicated.
Here is a simple adaptable solution using two event listeners. Delta is the distance in pixels that you must move horizontally or vertically between the up and down events for the code to classify it as a drag rather than a click. This is because sometimes you will move the mouse or your finger a few pixels before lifting it.
const delta = 6;
let startX;
let startY;
element.addEventListener('mousedown', function (event) {
startX = event.pageX;
startY = event.pageY;
});
element.addEventListener('mouseup', function (event) {
const diffX = Math.abs(event.pageX - startX);
const diffY = Math.abs(event.pageY - startY);
if (diffX < delta && diffY < delta) {
// Click!
}
});
Cleaner ES2015
let drag = false;
document.addEventListener('mousedown', () => drag = false);
document.addEventListener('mousemove', () => drag = true);
document.addEventListener('mouseup', () => console.log(drag ? 'drag' : 'click'));
Didn't experience any bugs, as others comment.
In case you are already using jQuery:
var $body = $('body');
$body.on('mousedown', function (evt) {
$body.on('mouseup mousemove', function handler(evt) {
if (evt.type === 'mouseup') {
// click
} else {
// drag
}
$body.off('mouseup mousemove', handler);
});
});
This should work well. Similar to the accepted answer (though using jQuery), but the isDragging flag is only reset if the new mouse position differs from that on mousedown event. Unlike the accepted answer, that works on recent versions of Chrome, where mousemove is fired regardless of whether mouse was moved or not.
var isDragging = false;
var startingPos = [];
$(".selector")
.mousedown(function (evt) {
isDragging = false;
startingPos = [evt.pageX, evt.pageY];
})
.mousemove(function (evt) {
if (!(evt.pageX === startingPos[0] && evt.pageY === startingPos[1])) {
isDragging = true;
}
})
.mouseup(function () {
if (isDragging) {
console.log("Drag");
} else {
console.log("Click");
}
isDragging = false;
startingPos = [];
});
You may also adjust the coordinate check in mousemove if you want to add a little bit of tolerance (i.e. treat tiny movements as clicks, not drags).
If you feel like using Rxjs:
var element = document;
Rx.Observable
.merge(
Rx.Observable.fromEvent(element, 'mousedown').mapTo(0),
Rx.Observable.fromEvent(element, 'mousemove').mapTo(1)
)
.sample(Rx.Observable.fromEvent(element, 'mouseup'))
.subscribe(flag => {
console.clear();
console.log(flag ? "drag" : "click");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/#reactivex/rxjs#5.4.1/dist/global/Rx.js"></script>
This is a direct clone of what #wong2 did in his answer, but converted to RxJs.
Also interesting use of sample. The sample operator will take the latest value from the source (the merge of mousedown and mousemove) and emit it when the inner observable (mouseup) emits.
As mrjrdnthms points out in his comment on the accepted answer, this no longer works on Chrome (it always fires the mousemove), I've adapted Gustavo's answer (since I'm using jQuery) to address the Chrome behavior.
var currentPos = [];
$(document).on('mousedown', function (evt) {
currentPos = [evt.pageX, evt.pageY]
$(document).on('mousemove', function handler(evt) {
currentPos=[evt.pageX, evt.pageY];
$(document).off('mousemove', handler);
});
$(document).on('mouseup', function handler(evt) {
if([evt.pageX, evt.pageY].equals(currentPos))
console.log("Click")
else
console.log("Drag")
$(document).off('mouseup', handler);
});
});
The Array.prototype.equals function comes from this answer
Using jQuery with a 5 pixel x/y theshold to detect the drag:
var dragging = false;
$("body").on("mousedown", function(e) {
var x = e.screenX;
var y = e.screenY;
dragging = false;
$("body").on("mousemove", function(e) {
if (Math.abs(x - e.screenX) > 5 || Math.abs(y - e.screenY) > 5) {
dragging = true;
}
});
});
$("body").on("mouseup", function(e) {
$("body").off("mousemove");
console.log(dragging ? "drag" : "click");
});
You could do this:
var div = document.getElementById("div");
div.addEventListener("mousedown", function() {
window.addEventListener("mousemove", drag);
window.addEventListener("mouseup", lift);
var didDrag = false;
function drag() {
//when the person drags their mouse while holding the mouse button down
didDrag = true;
div.innerHTML = "drag"
}
function lift() {
//when the person lifts mouse
if (!didDrag) {
//if the person didn't drag
div.innerHTML = "click";
} else div.innerHTML = "drag";
//delete event listeners so that it doesn't keep saying drag
window.removeEventListener("mousemove", drag)
window.removeEventListener("mouseup", this)
}
})
body {
outline: none;
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
overflow: hidden;
}
#div {
/* calculating -5px for each side of border in case border-box doesn't work */
width: calc(100vw - 10px);
height: calc(100vh - 10px);
border: 5px solid orange;
background-color: yellow;
font-weight: 700;
display: grid;
place-items: center;
user-select: none;
cursor: pointer;
padding: 0;
margin: 0;
}
<html>
<body>
<div id="div">Click me or drag me.</div>
</body>
</html>
Had the same problem recently with a tree list where the user can either click on the item or drag it, made this small Pointer class and put it in my utils.js
function Pointer(threshold = 10) {
let x = 0;
let y = 0;
return {
start(e) {
x = e.clientX;
y = e.clientY;
},
isClick(e) {
const deltaX = Math.abs(e.clientX - x);
const deltaY = Math.abs(e.clientY - y);
return deltaX < threshold && deltaY < threshold;
}
}
}
Here you can see it at work:
function Pointer(threshold = 10) {
let x = 0;
let y = 0;
return {
start(e) {
x = e.clientX;
y = e.clientY;
},
isClick(e) {
const deltaX = Math.abs(e.clientX - x);
const deltaY = Math.abs(e.clientY - y);
return deltaX < threshold && deltaY < threshold;
}
}
}
const pointer = new Pointer();
window.addEventListener('mousedown', (e) => pointer.start(e))
//window.addEventListener('mousemove', (e) => pointer.last(e))
window.addEventListener('mouseup', (e) => {
const operation = pointer.isClick(e)
? "Click"
: "Drag"
console.log(operation)
})
It's really this simple
var dragged = false
window.addEventListener('mousedown', function () { dragged = false })
window.addEventListener('mousemove', function () { dragged = true })
window.addEventListener('mouseup', function() {
if (dragged == true) { return }
console.log("CLICK!! ")
})
You honestly do not want to add a threshold allowing a small movement. The above is the correct, normal, feel of clicking on all desktop interfaces.
Just try it.
You can easily add an event if you like.
If just to filter out the drag case, do it like this:
var moved = false;
$(selector)
.mousedown(function() {moved = false;})
.mousemove(function() {moved = true;})
.mouseup(function(event) {
if (!moved) {
// clicked without moving mouse
}
});
Another solution for class based vanilla JS using a distance threshold
private initDetectDrag(element) {
let clickOrigin = { x: 0, y: 0 };
const dragDistanceThreshhold = 20;
element.addEventListener('mousedown', (event) => {
this.isDragged = false
clickOrigin = { x: event.clientX, y: event.clientY };
});
element.addEventListener('mousemove', (event) => {
if (Math.sqrt(Math.pow(clickOrigin.y - event.clientY, 2) + Math.pow(clickOrigin.x - event.clientX, 2)) > dragDistanceThreshhold) {
this.isDragged = true
}
});
}
Add inside the class (SOMESLIDER_ELEMENT can also be document to be global):
private isDragged: boolean;
constructor() {
this.initDetectDrag(SOMESLIDER_ELEMENT);
this.doSomeSlideStuff(SOMESLIDER_ELEMENT);
element.addEventListener('click', (event) => {
if (!this.sliderIsDragged) {
console.log('was clicked');
} else {
console.log('was dragged, ignore click or handle this');
}
}, false);
}
Pure JS with DeltaX and DeltaY
This DeltaX and DeltaY as suggested by a comment in the accepted answer to avoid the frustrating experience when trying to click and get a drag operation instead due to a one tick mousemove.
deltaX = deltaY = 2;//px
var element = document.getElementById('divID');
element.addEventListener("mousedown", function(e){
if (typeof InitPageX == 'undefined' && typeof InitPageY == 'undefined') {
InitPageX = e.pageX;
InitPageY = e.pageY;
}
}, false);
element.addEventListener("mousemove", function(e){
if (typeof InitPageX !== 'undefined' && typeof InitPageY !== 'undefined') {
diffX = e.pageX - InitPageX;
diffY = e.pageY - InitPageY;
if ( (diffX > deltaX) || (diffX < -deltaX)
||
(diffY > deltaY) || (diffY < -deltaY)
) {
console.log("dragging");//dragging event or function goes here.
}
else {
console.log("click");//click event or moving back in delta goes here.
}
}
}, false);
element.addEventListener("mouseup", function(){
delete InitPageX;
delete InitPageY;
}, false);
element.addEventListener("click", function(){
console.log("click");
}, false);
For a public action on an OSM map (position a marker on click) the question was: 1) how to determine the duration of mouse down->up (you can't imagine creating a new marker for each click) and 2) did the mouse move during down->up (i.e user is dragging the map).
const map = document.getElementById('map');
map.addEventListener("mousedown", position);
map.addEventListener("mouseup", calculate);
let posX, posY, endX, endY, t1, t2, action;
function position(e) {
posX = e.clientX;
posY = e.clientY;
t1 = Date.now();
}
function calculate(e) {
endX = e.clientX;
endY = e.clientY;
t2 = (Date.now()-t1)/1000;
action = 'inactive';
if( t2 > 0.5 && t2 < 1.5) { // Fixing duration of mouse down->up
if( Math.abs( posX-endX ) < 5 && Math.abs( posY-endY ) < 5 ) { // 5px error on mouse pos while clicking
action = 'active';
// --------> Do something
}
}
console.log('Down = '+posX + ', ' + posY+'\nUp = '+endX + ', ' + endY+ '\nAction = '+ action);
}
Based on this answer, I did this in my React component:
export default React.memo(() => {
const containerRef = React.useRef(null);
React.useEffect(() => {
document.addEventListener('mousedown', handleMouseMove);
return () => document.removeEventListener('mousedown', handleMouseMove);
}, []);
const handleMouseMove = React.useCallback(() => {
const drag = (e) => {
console.log('mouse is moving');
};
const lift = (e) => {
console.log('mouse move ended');
window.removeEventListener('mousemove', drag);
window.removeEventListener('mouseup', this);
};
window.addEventListener('mousemove', drag);
window.addEventListener('mouseup', lift);
}, []);
return (
<div style={{ width: '100vw', height: '100vh' }} ref={containerRef} />
);
})
If you want check the click or drag behavior of a specific element you can do this without having to listen to the body.
$(document).ready(function(){
let click;
$('.owl-carousel').owlCarousel({
items: 1
});
// prevent clicks when sliding
$('.btn')
.on('mousemove', function(){
click = false;
})
.on('mousedown', function(){
click = true;
});
// change mouseup listener to '.content' to listen to a wider area. (mouse drag release could happen out of the '.btn' which we have not listent to). Note that the click will trigger if '.btn' mousedown event is triggered above
$('.btn').on('mouseup', function(){
if(click){
$('.result').text('clicked');
} else {
$('.result').text('dragged');
}
});
});
.content{
position: relative;
width: 500px;
height: 400px;
background: #f2f2f2;
}
.slider, .result{
position: relative;
width: 400px;
}
.slider{
height: 200px;
margin: 0 auto;
top: 30px;
}
.btn{
display: flex;
align-items: center;
justify-content: center;
text-align: center;
height: 100px;
background: #c66;
}
.result{
height: 30px;
top: 10px;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css" />
<div class="content">
<div class="slider">
<div class="owl-carousel owl-theme">
<div class="item">
click me without moving the mouse
</div>
<div class="item">
click me without moving the mouse
</div>
</div>
<div class="result"></div>
</div>
</div>
from #Przemek 's answer,
function listenClickOnly(element, callback, threshold=10) {
let drag = 0;
element.addEventListener('mousedown', () => drag = 0);
element.addEventListener('mousemove', () => drag++);
element.addEventListener('mouseup', e => {
if (drag<threshold) callback(e);
});
}
listenClickOnly(
document,
() => console.log('click'),
10
);
The following coding is to detect the movement of mouseup and mousedown.
It shall work for most of the cases. It also depends
on how you treat mouseevent as Click.
In JavaScript, the detection is very simple. It does not concern how
long you press or movement between the mousedown and mouseup.
Event.detail would not reset to 1 when your mouse is moved between
the mousedown and mouseup.
If you need to differentiate the click and long press, you need to
check the difference in event.timeStamp too.
// ==== add the code at the begining of your coding ====
let clickStatus = 0;
(() => {
let screenX, screenY;
document.addEventListener('mousedown', (event) => ({screenX, screenY} = event), true);
document.addEventListener('mouseup', (event) => (clickStatus = Math.abs(event.screenX - screenX) + Math.abs(event.screenY - screenY) < 3), true);
})();
// ==== add the code at the begining of your coding ====
$("#draggable").click(function(event) {
if (clickStatus) {
console.log(`click event is valid, click count: ${event.detail}`)
} else {
console.log(`click event is invalid`)
}
})
<!doctype html>
<html lang="en">
<!-- coding example from https://jqueryui.com/draggable/ -->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Draggable - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<style>
#draggable { width: 150px; height: 150px; padding: 0.5em; }
</style>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#draggable" ).draggable();
} );
</script>
</head>
<body>
<div id="draggable" class="ui-widget-content">
<p>Drag me around</p>
</div>
</body>
</html>
Is very easy,
el = document.getElementById("your_id");
var isDown = false;
el.addEventListener('mousedown', function () {
isDown = true;
});
el.addEventListener('mouseup', function () {
isDown = false;
});
el.addEventListener('mousemove', function () {
if (isDown) {
// your code goes here
}
});

Categories

Resources