I'm trying to write a simple html based drawing application (standalone simplified code attached bellow). I've tested this on the following devices:
iPad 1 and 2: Works great
ASUS T101 running Windows: Works great
Samsung Galaxy Tab: Extremely slow and patchy -- unusable.
Lenovo IdeaPad K1: Extremely slow and patchy -- unusable.
Asus Transformer Prime: Noticeable lag compare with the iPad -- close to usable.
The Asus tablet is running ICS, the other android tablets are running 3.1 and 3.2. I tested using the stock Android browser. I also tried the Android Chrome Beta, but that was even worse.
Here's a video which demonstrates the issue: http://www.youtube.com/watch?v=Wlh94FBNVEQ
My questions is why are the Android tablets so slow? Am I doing something wrong or is it an inherit problem with Android OS or browser, or is there anything I can do about it in my code?
multi.html:
<html>
<body>
<style media="screen">
canvas { border: 1px solid #CCC; }
</style>
<canvas style="" id="draw" height="450" width="922"></canvas>
<script class="jsbin" src="jquery.js"></script>
<script src="multi.js"></script>
</body>
</html>
multi.js:
var CanvasDrawr = function(options) {
// grab canvas element
var canvas = document.getElementById(options.id),
ctxt = canvas.getContext("2d");
canvas.style.width = '100%'
canvas.width = canvas.offsetWidth;
canvas.style.width = '';
// set props from options, but the defaults are for the cool kids
ctxt.lineWidth = options.size || Math.ceil(Math.random() * 35);
ctxt.lineCap = options.lineCap || "round";
ctxt.pX = undefined;
ctxt.pY = undefined;
var lines = [,,];
var offset = $(canvas).offset();
var eventCount = 0;
var self = {
// Bind click events
init: function() {
// Set pX and pY from first click
canvas.addEventListener('touchstart', self.preDraw, false);
canvas.addEventListener('touchmove', self.draw, false);
},
preDraw: function(event) {
$.each(event.touches, function(i, touch) {
var id = touch.identifier;
lines[id] = { x : this.pageX - offset.left,
y : this.pageY - offset.top,
color : 'black'
};
});
event.preventDefault();
},
draw: function(event) {
var e = event, hmm = {};
eventCount += 1;
$.each(event.touches, function(i, touch) {
var id = touch.identifier,
moveX = this.pageX - offset.left - lines[id].x,
moveY = this.pageY - offset.top - lines[id].y;
var ret = self.move(id, moveX, moveY);
lines[id].x = ret.x;
lines[id].y = ret.y;
});
event.preventDefault();
},
move: function(i, changeX, changeY) {
ctxt.strokeStyle = lines[i].color;
ctxt.beginPath();
ctxt.moveTo(lines[i].x, lines[i].y);
ctxt.lineTo(lines[i].x + changeX, lines[i].y + changeY);
ctxt.stroke();
ctxt.closePath();
return { x: lines[i].x + changeX, y: lines[i].y + changeY };
},
};
return self.init();
};
$(function(){
var drawr = new CanvasDrawr({ id: "draw", size: 5 });
});
Looking at your code, you should do some optimizing. Right off the bat, never use jQuery's $.each() to do loops. Also, every time you poll the left, top, width or height of something, you're causing the browser to stop what it's doing, repaint the entire screen, and fetch you the most accurate values. Store these values in javascript variables instead. Use google chrome's timeline feature to find and eliminate unecessary paints and reflows. Here are some helpful links:
Nicholas C. Zakas Gives You Some Tips on avoiding Reflows.
http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
Here is Zakas giving his presentation to Google programmers:
http://www.youtube.com/watch?v=mHtdZgou0qU
Paul Irish Speeds up a slow JavaScript in front of your eyes:
http://www.youtube.com/watch?v=Vp524yo0p44
Please note, at the time of that video, the timeline was a beta feature in Chrome. It's now standard in Chrome 20. If you don't see it, update your Chrome.
Unfortunately... Even with all these optimizations... as of 2012 ...
MOST ANDROID DEVICES ARE STILL SLOW :-(
The touch events don't fire as rapidly as they do in Apple devices because Apple devices just have better hardware than most devices running Android's OS. There are fast Android tablets and phones out there, but they usually cost as much as much as the Apple devices--probably because they have similar hardware specs. Apple devices have special floating-point math chips and graphics chips in addition to the main CPU. Many Android devices do not contain those extra chips, instead they have virtual floating-point math chips.
The only thing you can do to accommodate slower Android devices is to detect them and gracefully degrade the user experience. For example, I created a draggable product carousel. For Androids, I eliminate the drag option and add clickable scroll arrows that move the carousel to the left or right a fixed set of pixels at a time.
The only way to really know where and why your code is underperforming is to profile it.
Chrome Mobile lets you connect to the WebKit inspector from your desktop, giving you access to the fantastic debugging tools you're used to in Chrome's Developer Tools.
Once you're connected to Chrome Mobile, profile your script and see what functions are chewing up CPU time. Then you'll be able to start figuring out how to optimize those functions.
Related
I'm working on something which is more or less a drawing app. I need to support Microsoft Edge and there's the problem. As soon as you want to catch the touchmove or pointermove event, Edge tries to scroll (even if it's not possible). Here's an example. Just try to draw something there (touch/pen), mouse works perfectly even in Edge. In every other browser (except maybe IE) it works very well.
I have no idea why IE is ignoring the preventDefault() function. Has someone an idea?
Sample: https://jsfiddle.net/ryqagkeb/
Actually doesn't work with my Surface Pro & Surface Pen on Chrome and Edge.
canvas = document.getElementsByTagName('canvas')[0];
ctx = canvas.getContext('2d');
start = 0;
canvas.onpointerdown = function(evt) {
start = 1;
}
canvas.onpointermove = function(evt) {
if(start) {
ctx.fillRect(evt.clientX, evt.clientY, 5,5);
}
}
canvas.onpointerup = function(evt) {
start = 0;
}
So I think I've found the solution myself and posting it now because I think it's pretty helpful. It seems the CSS property "touch-action" can solve the problem.
Setting:
canvas {
touch-action: none;
}
It seems like it fixes the problem.
For a parallax-effect, I created a simple script in native Javascript, but it seems to fail somewhere I can't see. That's why I already added the requestAnimationFrame-functionality, but it doesn't seem to really help.
My relevant code is as follows:
var $parallax, vh;
$(document).ready(function() {
$parallax = $('.parallax');
vh = $(window).height();
$parallax.parallaxInit();
});
$(window).resize(function() {
vh = $(window).height();
$parallax.parallaxInit();
});
$.fn.parallaxInit = function() {
var _ = this;
_.find('.parallax-bg')
.css('height', vh + (vh * .8) );
}
//call function on scroll
$(window).scroll(function() {
window.requestAnimationFrame(parallax);
});
var parallaxElements = document.getElementsByClassName('parallax'),
parallaxLength = parallaxElements.length;
var el, scrollTop, elOffset, i;
function parallax(){
for( i = 0; i < parallaxLength; i++ ) {
el = parallaxElements[i];
elOffset = el.getBoundingClientRect().top;
// only change if the element is in viewport - save resources
if( elOffset < vh && elOffset + el.offsetHeight > 0) {
el.getElementsByClassName('parallax-bg')[0].style.top = -(elOffset * .8) + 'px';
}
}
}
I think it's weird that this script by Hendry Sadrak runs better than my script (on my phone) while that is not really optimised, as far as I can tell.
Update: I checked if getBoundingClientRect might be slower in some freak of Javascript, but it's about 78% faster: https://jsperf.com/parallax-test
So here is the downlow on JS animations on mobile devices. Dont rely on them.
The reason is that mobile devices have a battery and the software is designed to minimize battery load. One of the tricks that manufacturers use (Apple does this on all their mobile devices) is temporarily pause script execution while scrolling. This is particularly noticeable with doing something like parallax. What you are seeing is the code execution - then you scroll, it pauses execution, you stop scrolling and the animation unpauses and catches up. But that is not all. iOS uses realtime prioritization of the UI thread - which means, your scrolling takes priority over all other actions while scrolling - which will amplify this lag.
Use CSS animation whenever possible if you need smooth animation on mobile devices. The impact is seen less on Android as the prioritization is handled differently, but some lag will likely be noticeable.
Red more here: https://plus.google.com/100838276097451809262/posts/VDkV9XaJRGS
I fixed it! I used transform: translate3d instead, which works with the GPU instead of the CPU. Which makes it much smoother, even on mobile.
http://codepen.io/AartdenBraber/pen/WpaxZg?editors=0010
Creating new jQuery objects is pretty expensive, so ideally you want to store them in a variable if they are used more than once by your script. (A new jQuery object is created every time you call $(window)).
So adding var $window = $(window); at the top of your script and using that instead of calling $(window) again should help a lot.
I have a site here:
www.completefaq.com
The main page contains a slider, which I built on my own. It changes the slides automatically after 8 secs. and on a click on forward or backward button. But, the problem is that I want it to scroll even when one tries to just slide it on a touchscreen.
Any help is appreciated. I can only use CSS, JavaScript, HTML and PHP, because other APIs such as JQuery, tend to slowdown the website.
Since you don't want a jQuery plugin, it's going to be non trivial due to the differences between the various touch platforms, and in the end you would probably end up in reinventing the wheel trying to get the abstraction you need. Above all, your effort will clearly depend on the level of accuracy you want to achieve, so it's advisable that you don't try to make the images respond to every minimal touch event.
I believe your best bet is to use Tocca.js, a very promising, standalone script which is "super lightweight" (only 1kB) and aims to normalize touch events among existing devices.
Hammer.js is more accurate, but could be a bit heavier in your case.
QuoJS is also good but it's not focused only on touch events.
You may find this and this SO question interesting. Finally, you can always take inspiration from the several existing touch-enabled javascript image sliders.
Here is a very lightweight script to start with.
Put this script somewhere below your div#featured, or execute it on dom ready.
You may want ajust the minimum swipe time (200ms) and minimum swipe distance (50px):
var featured = document.getElementById("featured");
if( "ontouchstart" in window ) {
var touchStart = function(evt) {
var startTime = (new Date()).getTime();
var startX = evt.changedTouches[0].pageX;
var touchEnd = function(evt) {
document.removeEventListener("touchend", touchEnd);
var diffX = evt.changedTouches[0].pageX - startX;
var elapsed = (new Date()).getTime() - startTime;
console.log( "Swiped " + diffX + " pixels in " + elapsed + " milliseconds" );
if( elapsed < 200 && Math.abs(diffX) > 50 ) {
( diffX < 0 ) ? slideright() : slideleft();
}
}
document.addEventListener("touchend", touchEnd);
};
featured.addEventListener("touchstart", touchStart);
}
I understand that you want to keep your website light, but if you wan't more advanced gesture recognition that this simple swipe, you will have to go with some kind of framework.
I am trying to find a script that detects if a device places position: fixed elements relative to the ViewPort and not to the entire document.
Currently, standard desktop browsers and Mobile Safari (for iOS 5) do so, whereas Android devices place the fixed elements relative to the entire document.
I have found a couple of tests to detect this, but none of the seem to work:
http://kangax.github.com/cft/ Gives me a false positive when I pass it from an Android device.
https://gist.github.com/1221602 Gives me a false negative when I pass it in an iPhone with iOS 5.
Does anybody know where to find / how to write a test that actually detects that? I don't want to rely on browser sniffing.
According to the contributors at Modernizr, you cannot do this without detecting the browser in use. The contributors are quite well-established in the field.
Testing for position: fixed on iOS and Android devices is listed under the Undetectables wiki page in the Modernizr project.
The MobileHTML5 website lists the support for position:fixed. http://mobilehtml5.org/
Actually, the guys from the Filament Group did a smart thing with their Fixedfixed putting the user agent string of known false positives in their test.
Check it # http://github.com/filamentgroup/fixed-fixed
Someone could complete it with some false negatives too, and make it a modernizr aditional featur test.
I've created another check if position:fixed is really supported in browser. It creates fixed div and try to scroll and check if the position of div changed.
function isPositionFixedSupported(){
var el = jQuery("<div id='fixed_test' style='position:fixed;top:1px;width:1px;height:1px;'></div>");
el.appendTo("body");
var prevScrollTop = jQuery(document).scrollTop();
var expectedResult = 1+prevScrollTop;
var scrollChanged = false;
//simulate scrolling
if (prevScrollTop === 0) {
window.scrollTo(0, 1);
expectedResult = 2;
scrollChanged = true;
}
//check position of div
suppoorted = (el.offset().top === expectedResult);
if (scrollChanged) {
window.scrollTo(0, prevScrollTop);
}
el.remove();
return suppoorted;
}
This function was tested in Firefox 22, Chrome 28, IE 7-10, Android Browser 2.3.
This works in all my test browsers:
<body onresize="handleResize()">
// Aint perfect, but works well enough!
function handleResize()
{
// Document.ready sets initialWidth = window.innerWidth;
var zoomAmount = Math.round((initialWidth / window.innerWidth) * 100);
$('#db').html(zoomAmount);
}
On iPhone when I use two fingers to zoom in and out, this number is not updated.
How can I trigger an event when the iPhone user zooms?
You can't. When you pinch to zoom on the iPhone web kit, you're not actually resizing the view of the web page in the same sense as when you resize a browser window -- notice that the content doesn't reflow. All the iPhone is doing is blowing up a certain section of the complete web content.