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.
Related
I am looking into creating a website which will serve as a a digital leaflet for a musical theatre. The idea is to have an autoscrolling credits list as landingpage. I've looked at examples on codepen to see how this effect is been achieved. But I would also like the user to interact and scroll themselves if they want to. When they stop scrolling the credits will turn back to autoscroll. I didn't find any example who tackles this issue. Does someone of you know a script (JS, or plain css…) that can help me with this?
The most straightforward way is to set up a requestAnimationFrame() function and increment the value accordingly, then set the scroll position to it.
Then add the wheel event to detect when a user scrolls (don't use the 'scroll' event though, it already gets called when you change the scrollTop value of the body), also don't forget to cancel the requestAnimationFrame() function. The code would look something like this:
let body = document.body,
starter = document.querySelector("h1"),
scroll_counter = 0,
scrolled,
auto_scroll_kicked = false;
starter.addEventListener("click", start_scrolling);
function start_scrolling() {
auto_scroll_kicked = true;
body.offsetHeight > scroll_counter
? (scroll_counter += 1.12)
: (scroll_counter = body.offsetHeight);
document.documentElement.scrollTop = scroll_counter;
scroller = window.requestAnimationFrame(start_scrolling);
if (scroll_counter >= body.offsetHeight) {
window.cancelAnimationFrame(scroller);
}
}
window.addEventListener("wheel", (e) => {
if (auto_scroll_kicked) {
window.cancelAnimationFrame(scroller);
scroll_counter = 0;
}
});
Play with the codepen if you'd like:
https://codepen.io/SaltyMedStudent/pen/QWqVwaR?editors=0010
There are many options to use: easing functions and etc, but hope this will suffice for now.
In your auto scroll routine before changing position check if previous position is the same as current scrolling position, if it's not - the user scrolled it:
let el = document.documentElement,
footer = document.getElementById("status").querySelectorAll("td"),
scroll_position = 0,
scroll_speed = 0,
scroll_delta = 1.12,
scroller,
status = "stopped";
el.addEventListener("click", scroll);
info();
function scroll(e)
{
if (e.type == "click")
{
window.cancelAnimationFrame(scroller);
scroll_position = el.scrollTop; //make sure we start from current position
scroll_speed++; //increase speed with each click
info("auto scroll");
}
//if previous position is different, this means user scrolled
if (scroll_position != el.scrollTop)
{
scroll_speed = 0;
info("stopped by user");
return;
}
el.scrollTop += scroll_delta * scroll_speed; //scroll to new position
scroll_position = el.scrollTop; //get the current position
//loop only if we didn't reach the bottom
if (el.scrollHeight - el.scrollTop - el.clientHeight > 0)
{
scroller = window.requestAnimationFrame(scroll); //loop
}
else
{
el.scrollTop = el.scrollHeight; //make sure it's all the way to the bottom
scroll_speed = 0;
info("auto stopped");
}
}
function info(s)
{
if (typeof s === "string")
status = s;
footer[1].textContent = el.scrollTop;
footer[3].textContent = scroll_speed;
footer[5].textContent = status;
}
//generate html demo sections
for(let i = 2, section = document.createElement("section"); i < 6; i++)
{
section = section.cloneNode(false);
section.textContent = "Section " + i;
document.body.appendChild(section);
}
//register scroll listener for displaying info
window.addEventListener("scroll", info);
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body
{
font-family: "Roboto", Arial;
user-select: none;
}
section
{
min-height: 100vh;
font-size: 3em;
font-weight: 500;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
section:nth-child(even)
{
background: #0b0d19;
}
section:nth-child(odd)
{
background: #131524;
}
#status
{
position: fixed;
bottom: 0;
color: #fff;
margin: 0.5em;
}
#status td:first-of-type
{
text-align: end;
padding-right: 0.4em;
}
#status td:last-of-type
{
font-weight: bold;
}
<section>
Click to start Scrolling
</section>
<table id="status">
<tr><td>position:</td><td></td></tr>
<tr><td>speed:</td><td></td></tr>
<tr><td>status:</td><td></td></tr>
</table>
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>
Below you will find a script I created for smooth scrolling when clicking local links. It is done via transform (no jQuery). As seen, I have implemented it using both inline CSS as well as external style sheets. I recommend the inline version, as it might be difficult to guess the index of the relevant style sheet.
The problem however, is that the actual movement of the scrollbar, happens after the transform is applied. Thus, if you click a link before scrolling transition is done, the code misbehaves.
Any thoughts on a solution to this?
EDIT:
I know there are jQuery solutions and third party polyfill libraries out there. My goal, however, was to recreate the jQuery functionality in plain vanilla JavaScript.
My Script:
// Get style rule's declaration block
function getStyleDeclaration(styleSheet, selectorText) {
const rules = styleSheet.cssRules;
return Array.from(rules).find(r => r.selectorText === selectorText).style;
// for (let i = 0; i < rules.length; i += 1) {
// if (rules[i].selectorText === selectorText) return rules[i].style;
// }
}
// Get specific style sheet, based on its title
// Many style sheets do not have a title however
// Which is main reason it is preferred to work with
// inline styles instead
function getStyleSheet(title) {
const styleSheets = document.styleSheets;
return Array.from(styleSheets).find(s => s.title === title);
// for (let i = 0; i < styleSheets.length; i += 1) {
// if (styleSheets[i].title === title) return styleSheets[i];
// }
}
function scrollToElement_ExternalStyleSheet(anchor, target) {
anchor.addEventListener("click", e => {
e.preventDefault();
const time = 1000;
// Distance from viewport to topof target
const distance = -target.getBoundingClientRect().top;
// Modify external style sheet
const transStyle = getStyleDeclaration(document.styleSheets[1], ".trans");
transStyle.transform = "translate(0, " + distance + "px)";
transStyle.transition = "transform " + time + "ms ease";
const root = document.documentElement; // <html> element
root.classList.add("trans");
window.setTimeout(() => {
root.classList.remove("trans");
root.scrollTo(0, -distance + window.pageYOffset);
}, time);
});
}
function scrollToElement_InlineStyle(anchor, target) {
const root = document.documentElement;
anchor.addEventListener('click', e => {
e.preventDefault();
const time = 900;
const distance = -target.getBoundingClientRect().top;
root.style.transform = 'translate(0, ' + distance + 'px)';
root.style.transition = 'transform ' + time + 'ms ease';
window.setTimeout(() => {
root.scrollTo(0, -distance + window.pageYOffset);
root.style.transform = null; // Revert to default
root.style.transition = null;
}, time);
});
}
function applySmoothScroll() {
const anchors = document.querySelectorAll("a");
const localAnchors = Array.from(anchors).filter(
a => a.getAttribute("href").indexOf("#") != -1
);
localAnchors.forEach(a => {
const targetString = a.getAttribute("href");
const target = document.querySelector(targetString);
// scrollToElement_ExternalStyleSheet(a, target);
scrollToElement_InlineStyle(a, target);
});
}
applySmoothScroll();
.box {
padding-bottom: 300px;
padding-top: 0.5rem;
background-color: orange;
text-align: center;
font-size: 200%;
}
.box:nth-child(even) {
background-color: lightblue;
color: white;
}
a {
color: black;
}
body {
margin: 0;
}
.trans {
transform: translate(0, -100px);
transition: transform 900ms ease;
}
<div id="s0" class="box">Click Me!</div>
<div id="s1" class="box">Click Me!</div>
<div id="s2" class="box">Click Me!</div>
CLICK FOR FIDDLE
Below is a fully functional full page touch slider I have created using hammer.js
You can drag, swipe or pan to navigate between pages.
The slider works as expected but I am now trying to create fallback navigation by adding two buttons so paging left and right can occur on click also.
QUESTION
How can the hammer swipe left or right be called on click? (Javascript or jQuery).
CURRENT ATTEMPT
$('#Left').on('click', function() {
HammerCarousel(document.querySelector('.Swiper'), 'Left');
});
FULL CODE
function swipe() {
var reqAnimationFrame = (function () {
return window[Hammer.prefixed(window, "requestAnimationFrame")] || function (callback) {
setTimeout(callback, 1000 / 60);
}
})();
function dirProp(direction, hProp, vProp) {
return (direction & Hammer.DIRECTION_HORIZONTAL) ? hProp : vProp
}
function HammerCarousel(container, direction) {
this.container = container;
this.direction = direction;
this.panes = Array.prototype.slice.call(this.container.children, 0);
this.containerSize = this.container[dirProp(direction, 'offsetWidth', 'offsetHeight')];
this.currentIndex = 0;
this.hammer = new Hammer.Manager(this.container);
this.hammer.add(new Hammer.Pan({ direction: this.direction, threshold: 10 }));
this.hammer.on("panstart panmove panend pancancel", Hammer.bindFn(this.onPan, this));
this.show(this.currentIndex);
}
HammerCarousel.prototype = {
show: function (showIndex, percent, animate) {
showIndex = Math.max(0, Math.min(showIndex, this.panes.length - 1));
percent = percent || 0;
var className = this.container.className;
if (animate) {
if (className.indexOf('animate') === -1) {
this.container.className += ' animate';
}
} else {
if (className.indexOf('animate') !== -1) {
this.container.className = className.replace('animate', '').trim();
}
}
var paneIndex, pos, translate;
for (paneIndex = 0; paneIndex < this.panes.length; paneIndex++) {
pos = (this.containerSize / 100) * (((paneIndex - showIndex) * 100) + percent);
translate = 'translate3d(' + pos + 'px, 0, 0)';
this.panes[paneIndex].style.transform = translate;
this.panes[paneIndex].style.mozTransform = translate;
this.panes[paneIndex].style.webkitTransform = translate;
}
this.currentIndex = showIndex;
},
onPan: function (ev) {
var delta = dirProp(this.direction, ev.deltaX, ev.deltaY),
percent = (100 / this.containerSize) * delta,
animate = false;
if (ev.type == 'panend' || ev.type == 'pancancel') {
if (Math.abs(percent) > 20 && ev.type == 'panend') {
this.currentIndex += (percent < 0) ? 1 : -1;
}
percent = 0;
animate = true;
}
this.show(this.currentIndex, percent, animate);
}
};
var outer = new HammerCarousel(document.querySelector('.Swiper'), Hammer.DIRECTION_HORIZONTAL);
};
$(swipe);
html,
body,
.Page,
.Swiper{
position:relative;
height:100%;
}
.Swiper{
background:#666;
overflow:hidden;
}
.Swiper.animate > .Page{
transition:all .3s;
-webkit-transition:all .3s;
}
.Page{
position:absolute;
top:0;
left:0;
height:100%;
width:100%;
padding:0 10px;
font:42px Arial;
color:#fff;
padding-top:10%;
text-align:center;
}
.Page:nth-child(odd) {
background:#b00;
}
.Page:nth-child(even) {
background:#58c;
}
#Left,
#Right{
position:absolute;
top:0;
height:50px;
width:50px;
background:#fff;
text-align:center;
font:16px/3em Arial;
cursor:pointer;
}
#Left{
left:0;
}
#Right{
right:0;
}
<script src="http://hammerjs.github.io/dist/hammer.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="Swiper">
<div class="Page">PAGE 1<br/>DRAG LEFT</div>
<div class="Page">PAGE 2<br/>SWIPE ME</div>
<div class="Page">PAGE 3<br/>HOLD TO PAN</div>
<div class="Page">PAGE 4<br/>FLICK TO GO BACK</div>
</div>
<div id="Left">Left</div>
<div id="Right">Right</div>
I have crafted a jQuery solution for this that should satisfy the fallback you are looking for.
Some things to consider, though. In your example as well, page re-size is not accounted for. I have not done so in this either to remain consistent and solve the immediate issue, but you will notice I am grabbing the $('.Page').width(); as a variable in this solution. I would recommend re-assigning this value if you do account for re-sizing. Also, a mix of swiping/clicking will throw this off. I assume since you indicated this will be a fallback, the user will receive one of the two experiences. If not, we'll need a way to update tracker on swipe events as well.
You'll notice var tracker = { 'R': 0 }. While naming may not be the best, 'R' will account for how many right "swipes" (navigation clicks) the user has performed in a plus/minus 1 manner
<div id="Left" direction="L">Left</div>
<div id="Right" direction="R">Right</div>
$(function() {
var width = $('.Page').width();
var pages = $('.Page').length;
var tracker = { 'R': 0 }
$('#Right, #Left').click(function() {
$(this).attr('direction') === 'R' ?
((tracker.R < (pages - 1) ? tracker.R += 1 : pages)) :
(tracker.R > 0) ? (tracker.R -= 1) : 0;
$('.Swiper').animate({ 'scrollLeft': $('.Page').width() * tracker.R }, 250)
});
});
JSFiddle Link
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;
}