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>
Related
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>
I've run into a problem with running a loop to trigger a mousemove event on my HTML/CSS.
I know I can go through and get every individual ID on the HTML tags to execute the code the way I want. But I know there is a better way to do it with a loop of some sort and use far less code.
The images should follow the mouse while moving over the div with class mycard. Any suggestions or ideas on how to get it working properly would be very much appreciated.
I've tried running a loop to add the classes to divs but had no luck.
var mouseHover = document.getElementById('moveHover');
window.onmousemove = function(e) {
var x = e.clientX;
var y = e.clientY;
mouseHover.style.top = (y + 20) + 'px';
mouseHover.style.left = (x + 20) + 'px';
};
.mycard span {
position: absolute;
display: none;
z-index: 99;
}
.mycard:hover span {
display: block;
position: fixed;
overflow: hidden;
}
.imgHover a {
position: relative;
}
.imgHover span {
position: absolute;
display: none;
z-index: 99;
}
.imgHover a:hover span {
display: block;
position: fixed;
overflow: hidden;
}
<div class="imgHover mycard">
<div class="cardcost">
<p class="cardcosttext">2</p>
</div>
<div class="hscardepic">
<a style="margin-left: 1000%;vertical-align: middle;">
Doomsayer
<span id="moveHover">
<img src="Classic_Set/doomsayer.png" height="300" width="300" />
</span>
</a>
</div>
<div class="cardamount">
<p class="cardamounttext">×2</p>
</div>
</div>
If I understand what you're asking, you could use querySelectorAll to get the elements and forEach to move them:
// get the div that responds to mouse movement
const mycard = document.querySelector('.mycard');
// add a mousemove listener
mycard.addEventListener('mousemove', function(e) {
// get the DOM element with the mousemove listener from the event
const {target} = e;
// get img child elements of the target.
// (use whatever css selector you need here. doesn't have to img)
const images = target.querySelectorAll('img');
// iterate over each item...
images.forEach(image => {
// ...and do whatever you need to do with it
const x = e.clientX;
const y = e.clientY;
image.style.top = (y + 20) + 'px';
image.style.left = (x + 20) + 'px';
})
});
I'm also not entirely sure what your end-goal is, but I'll take a stab at it.
I would recommend changing moveHover to being the class instead of the ID. Then you could do something like this:
var mouseHover = null;
window.onmousemove = function (e) {
if(mouseHover != null){
var x = e.clientX;
var y = e.clientY;
mouseHover.style.top = (y+20) + 'px';
mouseHover.style.left = (x+20) + 'px';
}
};
function onHover(e){
mouseHover = e.target.querySelector('.moveHover');
}
var elements = document.getElementsByClassName('imgHover');
for(var i = 0; i < elements.length; i++){
elements[i].onmouseenter = onHover;
}
The loop runs one time to set the onmouseenter event. Sure beats moving all .moveHover elements all the time.
Im trying to learn polymer and im following this tutorial here :
https://codelabs.developers.google.com/codelabs/polymer-2-carousel/index.html?index=..%2F..%2Findex#1
however the tutorial doesnt show how to associate text to the image in the carousel, i.e. i want to have text change when i click the buttons on the carousel
<!--
#license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<!-- Load the Polymer.Element base class -->
<link rel="import" href="bower_components/polymer/polymer-element.html">
<link rel="import" href="my-mixin.html">
<dom-module id="my-carousel">
<template>
<!-- Styles MUST be inside template -->
<style>
:host {
display: block;
position: relative;
overflow: hidden;
}
div > ::slotted(:not([selected])) {
display: none;
}
button {
position: absolute;
top: calc(50% - 20px);
padding: 0;
line-height: 40px;
border: none;
background: none;
color: #DDD;
font-size: 40px;
font-weight: bold;
opacity: 0.7;
}
button:hover,
button:focus {
opacity: 1;
}
#prevBtn {
left: 12px;
}
#nextBtn {
right: 12px;
}
button[disabled] {
opacity: 0.4;
}
</style>
<div>
<slot></slot>
</div>
<div id="buttons"> <button id="prevBtn" on-click="previous">❮</button>
<button id="nextBtn" on-click="next">❯</button></div>
</template>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src="js/index.js"></script>
<script>
// Extend Polymer.Element with MyMixin
class MyCarousel extends MyMixin(Polymer.Element) {
static get is() { return 'my-carousel' }
_selectedChanged(selected, oldSelected) {
super._selectedChanged(selected, oldSelected);
if (selected) {
this.$.prevBtn.disabled = !selected.previousElementSibling;
this.$.nextBtn.disabled = !selected.nextElementSibling;
this._loadImage(selected);
this._loadImage(selected.previousElementSibling);
this._loadImage(selected.nextElementSibling);
} else {
this.$.prevBtn.disabled = true;
this.$.nextBtn.disabled = true;
}
}
previous() {
const elem = this.selected && this.selected.previousElementSibling;
if (elem && !this._touchDir) {
// Setup transition start state
const oldSelected = this.selected;
this._translateX(oldSelected, 0);
this._translateX(elem, -this.offsetWidth);
// Start the transition
this.selected = elem;
this._translateX(oldSelected, this.offsetWidth, true /* transition */);
this._translateX(elem, 0, true /* transition */);
}
}
next() {
const elem = this.selected && this.selected.nextElementSibling;
if (elem && !this._touchDir) {
// Setup transition start state
const oldSelected = this.selected;
this._translateX(oldSelected, 0);
this._translateX(elem, this.offsetWidth);
// Start the transition
this.selected = elem;
this._translateX(oldSelected, -this.offsetWidth, true /* transition */);
this._translateX(elem, 0, true /* transition */);
}
}
_loadImage(img) {
if (img && !img.src) {
img.src = img.getAttribute('data-src');
}
}
_translateX(elem, x, transition) {
elem.style.display = 'block';
elem.style.transition = transition ? 'transform 0.2s' : '';
elem.style.transform = 'translate3d(' + x + 'px, 0, 0)';
}
ready() {
super.ready();
requestAnimationFrame(this._installListeners.bind(this));
}
_installListeners() {
this.addEventListener('transitionend', this._resetChildrenStyles.bind(this));
this.addEventListener('touchstart', this._touchstart.bind(this));
this.addEventListener('touchmove', this._touchmove.bind(this));
this.addEventListener('touchend', this._touchend.bind(this));
}
_resetChildrenStyles() {
let elem = this.firstElementChild;
while (elem) {
elem.style.display = '';
elem.style.transition = '';
elem.style.transform = '';
elem = elem.nextElementSibling;
}
}
_touchstart(event) {
// No transition if less than two images
if (this.childElementCount < 2) {
return;
}
// Save start coordinates
if (!this._touchDir) {
this._startX = event.changedTouches[0].clientX;
this._startY = event.changedTouches[0].clientY;
}
}
_touchmove(event) {
// No transition if less than two images
if (this.childElementCount < 2) {
return;
}
// Is touchmove mostly horizontal or vertical?
if (!this._touchDir) {
const absX = Math.abs(event.changedTouches[0].clientX - this._startX);
const absY = Math.abs(event.changedTouches[0].clientY - this._startY);
this._touchDir = absX > absY ? 'x' : 'y';
}
if (this._touchDir === 'x') {
// Prevent vertically scrolling when swiping
event.preventDefault();
let dx = Math.round(event.changedTouches[0].clientX - this._startX);
const prevChild = this.selected.previousElementSibling;
const nextChild = this.selected.nextElementSibling;
// Don't translate past the current image if there's no adjacent image in that direction
if ((!prevChild && dx > 0) || (!nextChild && dx < 0)) {
dx = 0;
}
this._translateX(this.selected, dx);
if (prevChild) {
this._translateX(prevChild, dx - this.offsetWidth);
}
if (nextChild) {
this._translateX(nextChild, dx + this.offsetWidth);
}
}
}
_touchend(event) {
// No transition if less than two images
if (this.childElementCount < 2) {
return;
}
// Don't finish swiping if there are still active touches.
if (event.touches.length) {
return;
}
if (this._touchDir === 'x') {
let dx = Math.round(event.changedTouches[0].clientX - this._startX);
const prevChild = this.selected.previousElementSibling;
const nextChild = this.selected.nextElementSibling;
// Don't translate past the current image if there's no adjacent image in that direction
if ((!prevChild && dx > 0) || (!nextChild && dx < 0)) {
dx = 0;
}
if (dx > 0) {
if (dx > 100) {
if (dx === this.offsetWidth) {
// No transitionend will fire (since we're already in the final state),
// so reset children styles now
this._resetChildrenStyles();
} else {
this._translateX(prevChild, 0, true);
this._translateX(this.selected, this.offsetWidth, true);
}
this.selected = prevChild;
} else {
this._translateX(prevChild, -this.offsetWidth, true);
this._translateX(this.selected, 0, true);
}
} else if (dx < 0) {
if (dx < -100) {
if (dx === -this.offsetWidth) {
// No transitionend will fire (since we're already in the final state),
// so reset children styles now
this._resetChildrenStyles();
} else {
this._translateX(this.selected, -this.offsetWidth, true);
this._translateX(nextChild, 0, true);
}
this.selected = nextChild;
} else {
this._translateX(this.selected, 0, true);
this._translateX(nextChild, this.offsetWidth, true);
}
} else {
// No transitionend will fire (since we're already in the final state),
// so reset children styles now
this._resetChildrenStyles();
}
}
// Reset touch direction
this._touchDir = null;
}
}
// Register custom element definition using standard platform API
customElements.define(MyCarousel.is, MyCarousel);
</script>
</dom-module>
You cannot - not as it is done in the tutorial.
Your 'lorems' are hard coded inside index.html, so to achieve the behavior you are trying to get you would need to wrap them in another custom element, in a similar fashion my-carousel is structured, and use data binding to propagate change event between the two:
<my-carousel selected={{selected}}>
<img data-src="https://app-layout-assets.appspot.com/assets/bg1.jpg">
<img data-src="https://app-layout-assets.appspot.com/assets/bg2.jpg">
<img data-src="https://app-layout-assets.appspot.com/assets/bg3.jpg">
...
</my-carousel>
<my-text-selector selected={{selected}}>
<p>Lorem ipsum...</p>
<p>Lorem ipsum...</p>
<p>Lorem ipsum...</p>
...
<my-text-selector>
You will need to implement content switching based on changes to selected property. The above would also need to be wrapped in dom-bind as it's not inside a polymer managed element but in index.html.
Also look into Polymer Starter Kit for an example of using iron-pages element that basically manages content switching.
I'm trying to fade in/out and fix the blue div on the left when scrolled relative to the image blocks on the right.
http://www.warface.co.uk/#/testing/
pass: squared
.meta { /*This is the block I'm trying to stick/*
background: blue;
position: fixed;
width: 372px;
float: left;
z-index: 3;
right: 100%;
}
Here is the basics in JavaScript:
function controlMeta() {
var meta = document.querySelector("div.meta");
console.log(meta);
if (window.scrollY > 500) {
meta.style.display = "none";
} else {
meta.style.display = "block";
}
}
window.addEventListener("scroll", function () {
controlMeta();
})
You can get your elements scroll position with something like this:
document.getElementById("57513a9220c6475fb77061c5").getBoundingClientRect().top+window.scrollY
EDIT 1
Here is a method for associating elements with the meta box, based upon the previous:
//Load elements that affect the meta box
var meta = [];
var images = document.querySelector('.sqs-gallery').getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
meta.push({
node : images[i],
height : images[i].height,
//top is used with scroll position to determine which element we are looking at
top : images[i].getBoundingClientRect().top + window.scrollY
});
}
function controlMeta() {
meta.filter(function (el) {
//You might need to pad your filter a little
return window.scrollY < el.top && window.scrollY > el.top - el.height;
}).forEach(function (el) {
//These are the matching elements. I'm just fetching the src as an example
document.querySelector("div.meta div.body").innerHTML = el.node.src;
});
}
window.addEventListener("scroll", function () {
controlMeta();
});
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;
}