mimicking iPhone main screen slide in JavaScript - javascript

I'd like to mimick iPhone main screen in JavaScript on Safari / Chrome / Firefox.
By mimicking I mean:
- Having a couple of pages
- Switching between the pages by clicking & dragging / swiping with my mouse
- Having those dots from the bottom iPhone main screen displaying which page it is
The closest to what I want is:
http://jquery.hinablue.me/jqiphoneslide/
But the sliding doesn't work nearly as good as in iPhone (i have to slide first, and the animation appears after i release the mouse button), and there are no dots at the bottom.

I solved the problem by using jQuery & jquery.slide-0.4.3.js .
jQuery Slide automatically slides between each page, so I had to add a mouse event (onMouseDrag) that stops automatic slide & reacts to user. It works very well.
This is what I added to jSlide
var jSlide = function(element, options)
{
element = $(element);
$('ul.layers li', element).sliderDisableTextSelect();
// my code here
var dragging = false;
var srcX;
var offsetX;
var diff;
this.manualDown = function(event) {
dragging = true;
srcX = event.pageX;
offsetX = parseFloat($('ul.layers li', element).css('marginLeft'));
obj.settings.easing = "easeOutExpo";
if(obj.settings.loopNr != null) {
obj.toggleLoop(0);
};
return false;
};
this.manualMove = function(event) {
if (dragging) {
diff = event.pageX - srcX;
$('ul.layers li', element).css('marginLeft',(offsetX+diff)+'px');
console.log((offsetX+diff)+'px');
};
return false;
};
this.manualUp = function(event) {
if (dragging) {
dragging = false;
if ((diff<-obj.settings.layerWidth/5) && (obj.settings.slidePos<obj.settings.layersSize-1)) {
obj.slideTo(parseInt(obj.settings.slidePos)+1);
} else if ((diff>obj.settings.layerWidth/5) && (obj.settings.slidePos>0)) {
obj.slideTo(parseInt(obj.settings.slidePos)-1);
} else { // if not slid far enough nor is it the last slide
obj.slideTo(obj.settings.slidePos);
};
};
};
this.manualLeave = function(event) {
if (dragging) {
dragging = false;
if ((diff<0) && (obj.settings.slidePos<obj.settings.layersSize-1)) {
obj.slideTo(parseInt(obj.settings.slidePos)+1);
} else if ((diff>0) && (obj.settings.slidePos>0)) {
obj.slideTo(parseInt(obj.settings.slidePos)-1);
} else { // if it's the last slide
obj.slideTo(obj.settings.slidePos);
};
};
};
element.mousedown(this.manualDown);
element.mousemove(this.manualMove);
element.mouseup(this.manualUp);
element.mouseleave(this.manualLeave);
And also, to prevent text selection when dragging with mouse I added before jSlide class declaration:
$.extend($.fn.disableTextSelect = function() {
return this.each(function(){
if($.browser.mozilla){//Firefox
$(this).css('MozUserSelect','none');
}else if($.browser.msie){//IE
$(this).bind('selectstart',function(){return false;});
}else{//Opera, etc.
$(this).mousedown(function(){return false;}); // this is handled in jSlide
}
});
});
$.extend($.fn.sliderDisableTextSelect = function() {
return this.each(function(){
if($.browser.mozilla){//Firefox
$(this).css('MozUserSelect','none');
}else if($.browser.msie){//IE
$(this).bind('selectstart',function(){return false;});
}else{//Opera, etc.
// $(this).mousedown(function(){return false;}); // this is handled in jSlide
}
});
});
I'm not sure if all the code is necessary... most probably you'll still need to tweak it after pasting into jquery.slide, but it should get you started..

Related

How to catch clicks on multiple images?

I have an iOS uiwebview with multiple imagemaps that I need to catch clicks on, so I can handle scaling on different iOS devices. The click handler I install works on the first image, but not on subsequent images. How do I make the click handler work on multiple images? The relevant code is below:
$.fn.imageMapSetup2 = function () {
$('img').each(function () {
if (typeof ($(this).attr('usemap')) == 'undefined') {
return;
}
var img = $(this);
// add click handler
img.on('click', function (event) {
img.imgClick(event);
});
});
};
$.fn.imgClick = function (mouseDown) {
mouseDown.preventDefault();
var $img = this;
var map = $img.attr('usemap').replace('#', '');
$('map[name="' + map + '"]').find('area').each(function () {
var $this = $(this),
coords = $this.attr('coords').split(',');
// lots of scaling code omitted
if (mouseX >= left && mouseX <= right &&
mouseY >= top && mouseY <= bottom) {
window.location = $this.attr('href');
}
});
};
FYI I have debugged the code in Safari and function imgClick() is not getting called for the second and subsequent images.
Add a click event listener to the parent element of the images. This could be the body element. Pass the event as an argument. Then, check the event, and use that variable to make changes to your image.
document.addEventListener("click", function (event) {
if (!event.target.tagName === "img") return;
if (typeof event.target.getAttribute("usemap") == "undefined") {
return;
}
imgClick(event);
});

Touch move fire only once when implementing "less" like scrolling on mobile

I'm trying to to implement touch scroll in less extension to jQuery Terminal. It work similar to less unix command.
I have this code:
self.touch_scroll(function(event) {
// how much difference changed since last touch move
var delta = event.current.clientY - event.previous.clientY;
var ret;
var interpreter = interpreters.top();
if (is_function(interpreter.touchscroll)) {
ret = interpreter.touchscroll(event, delta, self);
} else if (is_function(settings.touchscroll)) {
ret = settings.touchscroll(event, delta, self);
}
if (ret === true) {
return;
}
return false;
});
// make_callback_plugin is helper that use $.Callbacks and make sure that there is only
// one handler on the element
$.fn.touch_scroll = make_callback_plugin({
name: 'touch',
init: function(handler) {
var origin;
var previous;
$(this).on('touchstart.scroll', function(e) {
e = e.originalEvent;
if (e.touches.length === 1) {
previous = origin = e.touches[0];
}
}).on('touchmove.scroll', function(e) {
e = e.originalEvent;
console.log(!!origin + ' && ' + (e.touches.length) + ' === 1');
if (origin && e.touches.length === 1) {
var current = e.touches[0];
var ret = handler({
origin: origin,
previous: previous,
current: current
});
if (ret === false) {
// this don't change anything
e.preventDefault();
}
previous = current;
}
}).on('touchend.scroll', function() {
if (origin || previous) {
origin = previous = null;
}
});
},
destroy: function() {
$(this).off('touchstart.scroll touchmove.scroll touchend.scroll');
}
});
and inside less I have:
function scroll(delta, scroll_by) {
if (delta > 0) {
pos -= scroll_by;
if (pos < 0) {
pos = 0;
}
} else {
pos += scroll_by;
if (pos - 1 > lines.length - rows) {
pos = lines.length - rows + 1;
}
}
print();
return true;
}
term.push($.noop, {
onResize: refresh_view,
touchscroll: function(event, delta) {
console.log({delta});
var offset = Math.abs(delta);
// 14 is default height of single line in pixels
scroll(delta, Math.ceil(offset / 14));
return false;
},
mousewheel: function(event, delta) {
return scroll(delta, scroll_by);
},
I also have this css:
.terminal-less {
touch-action: none;
overscroll-behavior-y: contain;
}
on Mousewheel scrolling works good it scroll with the same amount of scroll_by which is by default 3 (seems about right). (pos is lines offset so if I use pos++ it move/scroll by one line, delta in touchscroll is positive or negative from about -20 to 20 pixels.
The problem I have and the question is, how can I make it scroll with the finger? it don't feel right. Also it scroll only once it don't move with the finger. touchmove fire only once, shoudn't it fire while I move the finger while touching the phone?
Anyone have experience with this type of touch scroll behavior?
I was searching for similar problem and didn't found solution. Do you know why touchmove could fire once? The only thing I can think of was textarea that is used as clipboard (on mobile it's also used to enable virtual keyboard), but I've set background to red and it don't move on Android. I was testing other code from this drawing demo:
https://zipso.net/a-simple-touchscreen-sketchpad-using-javascript-and-html5/
and it works fine, touch move keeps firing while you move the finger.
Any ideas? It will be hard to replicate but if somone is interested in investigation I can put all my code on github in jQuery Terminal repo (in some branch).
What's weird is that touchend don't fire after touchmove, it fire once only when I click on the terminal to enable keyboard.
I've tried to monkey patch jQuery on and log each time it fire but I didn't have any other event (that may prevent default behavior) also according to docs mouse events fire after touchend and those don't fire.
What you need is simple .terminal-wrapper { pointer-events: none; } (based on the devel branch). But with this rule you can't select the text, that's why you need to use it only for mobile devices.
I'm not sure if this will block the selection of text on mobile, but if so, you can try to add this on touchstart (or even on touchmove as the first instruction) and remove it on touchend.
Also, I had to add some JS code, because without it interpreter.touchScroll is undefined. But this is not the main cause of the problem.
interpreters = new Stack($.extend({}, settings.extra, {
name: settings.name,
prompt: prompt,
keypress: settings.keypress,
keydown: settings.keydown,
resize: settings.onResize,
greetings: settings.greetings,
mousewheel: settings.mousewheel,
touchScroll: settings.touchScroll, // NEW LINE
history: settings.history,
keymap: new_keymap
}, interpreter));
self.touch_scroll(function(event) {
var delta = event.current.clientY - event.previous.clientY;
var ret;
var interpreter = interpreters.top(); // NEW LINE
if (is_function(interpreter.touchScroll)) {
ret = interpreter.touchScroll(event, delta, self);
} else if (is_function(settings.touchScroll)) {
ret = settings.touchScroll(event, delta, self);
}
if (ret === true) {
return;
}
});
Without pointer-events: none;
With pointer-events: none;

Canceling a custom drag function when mouse slides off div

I have a draggable function in jquery to make it so I can drag and move elements on a div. Sometimes, when dragging the mouse comes off the div and I am not able to put back down the element.
I'm trying to add a keydown event for the escape button or something so that when pressed, the same thing happens on .on("mouseup", function(event) {
I've tried doing .on("mouseup keydown", function(event) { but it doesn't catch any keys that are being pressed.
Does anyone have any ideas on how I can cancel the drag? Either by a keydown or even on a mouseup regardless of if the mouse is on the div or not that is being dragged?
Just to be clear, the problem I am having is sometimes I will be dragging the element, I will mouseup but the mouse wasn't on the element when mouseup was called. Therefore, the element is still dragging and I no longer have my finger on the mouse and I have no way to stop the element from dragging to get it back on the document.
EDIT: Here is a jsfiddle, notice I am trying to get this to work on a scaled container. youtube video showing drag glitch
(function($) {
$.fn.drags = function(opt, callback) {
opt = $.extend({
handle: "",
cursor: "move"
}, opt);
if (opt.handle === "") {
var $el = this;
} else {
var $el = this.find(opt.handle);
}
return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
if (opt.handle === "") {
var $drag = $(this).addClass('draggable');
} else {
var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
}
var z_idx = $drag.css('z-index'),
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top + drg_h - e.pageY,
pos_x = $drag.offset().left + drg_w - e.pageX;
$drag.css('z-index', 1000).parents().on("mousemove", function(e) {
$('.draggable').offset({
top: e.pageY + pos_y - drg_h,
left: e.pageX + pos_x - drg_w
}).on("mouseup", function() {
$(this).removeClass('draggable').css('z-index', z_idx);
});
});
e.preventDefault();
}).on("mouseup", function(event) {
if (opt.handle === "") {
$(this).removeClass('draggable');
} else {
$(this).removeClass('active-handle').parent().removeClass('draggable');
}
if (typeof callback == 'function') {
alert("this is a callback");
}
});
}
})(jQuery);
Here are a few things that might work:
Instead of listening for mouseup on the target element, listen for it on document.body. That way it will fire regardless of if the cursor is over the dragged element.
If you want to cancel the drag when the cursor wanders out of the page, add an event listener for mouseleave on document.body and use it to cancel the drag.
If you make a code-pen (or similar) test case, I will be happy to dig into the code.
Edit__
Handling mouseleave on the document prevents it from getting stuck in a draggable state. It also fixes the multiplied movement that you were seeing.
$(document.body).on('mouseleave', function(){
$el.removeClass('draggable').css('z-index', z_idx);
});
Edit2__
Previous JSFiddle was incorrect.
https://jsfiddle.net/spk4523t/6/

elementFromPoint returns null after scrolling the page

I have a javascript bookmarklet I put together to make an arduous task a little more bearable. Essentially I am going through hundreds of pages of training material and making sure that all of it has been properly swapped from Helvetica to Arial. The bookmarklet code is below, but a quick breakdown is that it creates a mousemove event listener and a small, absolutely positioned div. On mousemove events, the div moves to the new mouse position (offset by 10px down and right), gets the element under the mouse with elementFromPoint and shows the font-family property for that element. oh and it changes it's background color based on whether Arial appears within the property.
var bodyEl=document.getElementsByTagName("body")[0];
var displayDiv=document.createElement("div");
displayDiv.style.position="absolute";
displayDiv.style.top="0px";
displayDiv.style.top="0px";
bodyEl.appendChild(displayDiv);
function getStyle(el,styleProp) {
var camelize = function (str) {
return str.replace(/\-(\w)/g, function(str, letter){
return letter.toUpperCase();
});
};
if (el.currentStyle) {
return el.currentStyle[camelize(styleProp)];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el,null)
.getPropertyValue(styleProp);
} else {
return el.style[camelize(styleProp)];
}
}
function getTheElement(x,y) {return document.elementFromPoint(x,y);}
fn_displayFont=function displayFont(e) {
e = e || window.event;
var divX=e.pageX+10;
var divY=e.pageY+10;
var font=getStyle(getTheElement(e.pageX,e.pageY),"font-family");
if (font.toLowerCase().indexOf("arial") != -1) {
displayDiv.style.backgroundColor = "green";
} else {
displayDiv.style.backgroundColor = "red";
}
displayDiv.style.top= divY.toString() + "px";
displayDiv.style.left= divX.toString() + "px";
displayDiv.style.fontFamily=font;
displayDiv.innerHTML=font;
}
window.addEventListener('mousemove', fn_displayFont);
document.onkeydown = function(evt) {
evt = evt || window.event;
if (evt.keyCode == 27) {
window.removeEventListener('mousemove', fn_displayFont);
bodyEl.removeChild(displayDiv);
}
};
(for the record, I stole the style determining code from an answer here on SO, but I lost the tab not long after. Thanks, anonymous internet guy!)
So this all works great - UNTIL I try to hover over a part of the page that is scrolled down from the top. The div sits at where it would be if I had the mouse on the very bottom of the screen while scrolled to the top of the page, and if I scroll down far enough firebug starts logging that e.pageX is undefined.
Any ideas?
Alrighty then, figured it out. I saw http://www.daniweb.com/web-development/javascript-dhtml-ajax/threads/276742/elementfrompoint-problems-when-window-has-been-scrolled- and thought it meant I had to minus the pageoffset straight away from the e.pageX/Y values, before I used it to calculate the div position or anything else, this just broke everything for me so I assumed it must have been unrelated - not so!
From what I now understand the elementFromPoint method takes a point relative in the current view of the browser, which is to say, base on the top left corner of what can currently be seen, not the page as a whole. I fixed it by just taking the offset from the X and Y values when I was getting the element. The now-working code is below.
var bodyEl=document.getElementsByTagName("body")[0];
var displayDiv=document.createElement("div");
displayDiv.style.position="absolute";
displayDiv.style.top="0px";
displayDiv.style.top="0px";
bodyEl.appendChild(displayDiv);
function getStyle(el,styleProp) {
var camelize = function (str) {
return str.replace(/\-(\w)/g, function(str, letter){
return letter.toUpperCase();
});
};
if (el.currentStyle) {
return el.currentStyle[camelize(styleProp)];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el,null)
.getPropertyValue(styleProp);
} else {
return el.style[camelize(styleProp)];
}
}
function getTheElement(x,y) {return document.elementFromPoint(x,y);}
fn_displayFont=function displayFont(e) {
e = e || window.event;
var divX=e.pageX + 10;
var divY=e.pageY + 10;
var font=getStyle(getTheElement(e.pageX - window.pageXOffset,e.pageY - window.pageYOffset),"font-family");
if (font.toLowerCase().indexOf("arial") != -1) {
displayDiv.style.backgroundColor = "green";
} else {
displayDiv.style.backgroundColor = "red";
}
displayDiv.style.top= divY.toString() + "px";
displayDiv.style.left= divX.toString() + "px";
displayDiv.style.fontFamily=font;
displayDiv.innerHTML=font;
}
document.addEventListener('mousemove', fn_displayFont);
document.onkeydown = function(evt) {
evt = evt || window.event;
if (evt.keyCode == 27) {
window.removeEventListener('mousemove', fn_displayFont);
bodyEl.removeChild(displayDiv);
}
};
Hmm instead of checking with the mouse, why not just check every leaf node? If any leaf node has a font-family of arial, then it should indicate that one of its ancestors has a font-family of Arial.
First you need to get jquery onto the page. Try this bookmarklet
Then run this code:
(function(){
var arialNodes = $('div:not(:has(*))').filter(function(){
return $(this).css('font-family').toLowerCase().indexOf("arial") != -1;
});
})();
The arialNodes variable should contain every leaf node that has a font-family of 'Arial'. You can then use this to figure out which parent element has the declaration.
Or if you just want to see if a page is compliant or not, just check the length.
Updated
Updated to reflect comments below
(function() {
var arialNodes = $('*:not(:has(*))', $('body')).filter(function() {
return $(this).css('font-family').toLowerCase().indexOf("arial") === -1;
});
var offendingParents = [];
arialNodes.each(function(){
var highestOffendingParent = $(this).parentsUntil('body').filter(function(){
return $(this).css('font-family').toLowerCase().indexOf("arial") === -1;
}).last();
if(offendingParents.indexOf(highestOffendingParent) === -1){
offendingParents.push(highestOffendingParent);
}
});
})();

Preventing swipe scrolling on an iPad HTML5 ad

I'm creating an ad for a magazine on iPad that has a long timeline that needs to be controlled by a horizontal scrollbar. The scrollbar had to be custom, and the best plugin for custom scrolling that I could find that worked on iPads was fleXcroll.
My problem is that I'm trying to disable the ability for the user to swipe scroll the timeline. It needs to be exclusively controlled by the scrollbar (since a swipe from the user should bring them to the next page of the magazine.)
I've been looking at this problem for the past two days and the closest I can get to solving it at the moment is by controlling the touch events for the div. When I use event.preventDefault() for touchstart and touchmove on the div it works partially. If the screen has not been moved, a swipe will not move the timeline. However, after using the scrollbar, if the timeline is still moving (iPads have a sort of easing when things are swiped so that whatever is being moved slows down before stopping) you can grab the timeline and move it by swiping it. Also, if you slowly let the scrollbar stop moving with your finger on it until it stops, you can then swipe the timeline again.
So, the problem is preventing the ability for users to swipe a certain div to the side in a magazine ad for iPad. The scrolling should ONLY be controlled by the scrollbar.
Any help is greatly appreciated! Thanks!
You can register JS to observe the swipe event and then just "ignore" them and prevent them from propagating further up the chain. Something like this might help:
(function($) {
$.fn.touchwipe = function(settings) {
var config = {
min_move_x: 20,
wipeLeft: function() { alert("left"); },
wipeRight: function() { alert("right"); },
preventDefaultEvents: true
};
if (settings) {
$.extend(config, settings);
}
this.each(function() {
var startX;
var isMoving = false;
function cancelTouch() {
this.removeEventListener('touchmove', onTouchMove);
startX = null;
isMoving = false;
}
function onTouchMove(e) {
if(config.preventDefaultEvents) {
e.preventDefault();
}
if(isMoving) {
var x = e.touches[0].pageX;
var dx = startX - x;
if(Math.abs(dx) >= config.min_move_x) {
cancelTouch();
if(dx > 0) {
config.wipeLeft();
}
else {
config.wipeRight();
}
}
}
}
function onTouchStart(e)
{
if (e.touches.length == 1) {
startX = e.touches[0].pageX;
isMoving = true;
this.addEventListener('touchmove', onTouchMove, false);
}
}
this.addEventListener('touchstart', onTouchStart, false);
});
return this;
};
})(jQuery);

Categories

Resources