CSS animation for both mouse hover and touch (iOS) - ios

Here is plnkr example.
Basically there is a style like that
.hover-block {
-webkit-transition: all 1s linear;
transition: all 1s linear;
}
.hover-block:active {
pointer-events: none;
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
.hover-block:hover {
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
I'm seeking to support evergreen and IE10/11, Chrome for Android (4.4+), Mobile Safari (iOS 7+), and it shouldn't hurt other touch events (swipe scrolling).
It seems to work as intended on Android and Chrome device emulation, non-sticky transform on touch is desired behaviour.
But somehow this plunker doesn't work on iOS webkit (iOS 8, all browsers), it does nothing on touch. I'm quite sure that exactly the same approach (block element, :active with pointer-events: none plus :hover) worked for me in iOS 8 before. How can it be fixed?
It looks like empty touchstart/touchend JS event handler or ontouchstart/ontouchend attribute can activate touch behaviour on iOS (can't be sure but it is possible that it happened to me before). Is it a known fix for the problem or there are less hacky ones, which iOS versions are affected?

In your html, instead of <body>, do <body ontouchstart="">
Or in html5, just <body ontouchstart>

So the issue you're running into is this: "The :active pseudo class matches when an element is being activated by the user". A standalone <div> element cannot be activated by the user and therefore will not be matched by the :active pseudo class.
If you look under Browser Compatibility in the :active MDN article you'll see that:
[1] By default, Safari Mobile does not use the :active state unless there is a touchstart event handler on the relevant element or on the <body>.
MDN has a list of pseudo classes that can be used and you might be able to find one that better fits your situation or adding a touchstart event should do the trick in Safari.
I was able to get your plnkr working really quick by changing the <div class="hover-block"></div> element to <button class="hover-block"></button> and changing .hover-block:active { to .hover-block:focus {. I also added display: block; border: 0; to .hover-block.
You, for obvious reasons, may not want to change your <div> to a <button> to get your effect to work, but by using an element that can be activated, using a different pseudo class, or adding an event that allows activation in your target browser, you should be able to achieve the effect you're looking for on mobile devices.
Hope that helps!

Related

Cordova iOS - transition causes page flash

I am developing an application in Cordova, where the user can switch between a few 'screens', which are just hidden divs brought into view by a transition.
The scrolling on iOS has been terrible, so I added -webkit-overflow-scrolling: touch to the container element and it sorted out the scrolling issue I had.
However, since then the page transitions cause the pages to flash each time the application moves to a new page.
Here is my CSS
.scrollable {
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
Once a button is pressed to proceed to the next page it uses this javascript code for the transition
this.lastScreen.getLayout().getElement().css({
'left': -$(window).width(),
'transition': 'left 0.25s ease-out'
});
this.currentScreen.getLayout().getElement().css({
'left': 0,
'transition': 'left 0.25s ease-out'
});
<div class="container scrollable">
//screen content here
</div>
If I remove the -webkit-overflow-scrolling: touch; from the scrollable class it works fine, no flash happens. However, the scrolling of the page is terrible.
I am running iOS 9.3.1. I read around and found out this may have been an issue from iOS 8+ but can't really find a difinitve answer to help me
I suggest you to use native transitions with cordova´s app.
http://plugins.telerik.com/cordova/plugin/native-page-transitions
Add this CSS to the classes that have transitions:
-webkit-transform: translate3d(0px,0px,0px);
It just force hardware acceleration, so it become smoother than the normal one, and probably fix your issues

-webkit-animation-play-state not working on iOS 8.1 (probably lower too)

I have an animation running on page load and with javascript I add a class containing the
-webkit-animation-play-state:paused;
Working fine on OSX safari and all other browsers (even PC) too but on mobile, only on iOS that the animation doesn't seem to paused when called.
Here's a fiddle on how the animation state is running and paused.
http://jsfiddle.net/uc9c5/2/
Try it on iOS, you'll see that it's totally ignored.
Workaround approach for iOS 8-9 Safari that use -webkit-animation: none !important; instead of -webkit-animation-play-state:paused;
This approach is for GWD, but can apply otherwise
Don't use Pause event in GWD (Google Web Designer)
Create normal event that calls a javascript function, set "-webkit-animation: none !important;" to the <div> (you can add/remove css class)
CSS Style
.no-animation {
-webkit-animation: none !important;
}
Javascript
div.className = div.className + " no-animation";
To resume, remove CSS class
Javascript
div.className = div.className.replace("no-animation", '');
Please note that when remove/pause animation, it will go back to frame 0 (00:00 s), so you may need to calculate the current opacity/position for the div
http://jsfiddle.net/duchuy/pczsufL9/

Unable to interact with content over HTML5 video in iOS

I've been given a design to implement which has a lightbox which has some content inside which includes links. This is all fine and working except for when it comes to iOS where it's not possible to interact with the content of a lightbox if its position happens to be on top of a video.
It's acting as though the video is on top of the lightbox content - even though it's behind. The issue occurs even with extremely simple barebones HTML.
Stripped back HTML:
<video id="home_video" controls preload="none" poster="http://www.videojs.com/img/poster.jpg" width="500">
<!-- video sources -->
</video>
<!-- positioned over the video -->
<div id="lightbox">
Not touchable on iOS
Touchable because it's not over a video
</div>
Associated stripped back styling:
#lightbox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.5);
}
#lightbox > a {
display: inline-block;
background: red;
padding: 20px;
}
#touchable {
margin-top: 400px; /* taller than video */
}
I've put together a jsfiddle example. It includes some JS which alerts when you've successfully clicked/touched a link. On desktop browsers it's possible to click both links, on iOS it's only possible to click the second.
It might be worth noting that the issue occurs whether the lightbox is pre-opened on page, or after being explicitly opened as in this jsfiddle
I can think of a number of ways of hacking around the problem - such as moving the video off screen, replacing it with its poster image, or by transforming the video using translateX to hide it, but I'd prefer to leave the video where it is, if possible.
Has anyone stumbled across this issue before and found a solution? Any pointers?
This is a quirk of Mobile Safari, where it intercepts all touch/click events for elements on top of a video element, regardless of z-index or DOM order, only when the controls attribute is set.
So the solution is to remove the controls attribute and implement your own custom controls wit Javascript. You can use existing open source players to provide these controls for you (e.g. jPlayer, videojs, etc.), but you need to be careful because some of them have a special case for iOS where they will just use the native player controls. I think this is because it's simpler than making those mouse-centric controls work with the quirks of iOS (like touch and lack of volume control). So you need to check the documentation to see if there's a flag to force the player to use its own controls rather than the built-in ones.

CSS3 Button properties are showing different in iPad

I am using css3 for "button", It is running well on every browser but it's showing different in iPad. I think it takes the default properties of iPad so I apply
"-webkit-appearance:none;" but it is not working.
My CSS Properties are - background:#1356b4; border:solid 1px #0e4189; border-radius:5px; transition:all 0.3s ease-in-out; -webkit-appearance:none;.
And html code is simple span class button and input.
Please help me if someone having any solutions.
By adding -webkit-appearance: none; we are telling mobile Safari that we explicitly don’t want our button to be styled like a native Apple UI control.

Is it possible to force ignore the :hover pseudoclass for iPhone/iPad users?

I have some css menus on my site that expand with :hover (without js)
This works in a semi-broken way on iDevices, for example a tap will activate the :hover rule and expand the menu, but then tapping elsewhere doesn't remove the :hover. Also if there is a link inside the element that is :hover'ed, you have to tap twice to activate the link (first tap triggers :hover, second tap triggers link).
I've been able to make things work nicely on iphone by binding the touchstart event.
The problem is that sometimes mobile safari still chooses to trigger the :hover rule from the css instead of my touchstart events!
I know this is the problem because when I disable all the :hover rules manually in the css, mobile safari works great (but regular browsers obviously don't anymore).
Is there a way to dynamically "cancel" :hover rules for certain elements when the user is on mobile safari?
See and compare iOS behavior here: http://jsfiddle.net/74s35/3/
Note: that only some css properties trigger the two-click behavior, e.g. display:none; but not background: red; or text-decoration: underline;
I found that ":hover" is unpredictable in iPhone/iPad Safari. Sometimes tap on element make that element ":hover", while sometimes it drifts to other elements.
For the time being, I just have a "no-touch" class at body.
<body class="yui3-skin-sam no-touch">
...
</body>
And have all CSS rules with ":hover" below ".no-touch":
.no-touch my:hover{
color: red;
}
Somewhere in the page, I have javascript to remove no-touch class from body.
if ('ontouchstart' in document) {
Y.one('body').removeClass('no-touch');
}
This doesn't look perfect, but it works anyway.
:hover isn't the issue here. Safari for iOS follows a very odd rule. It fires mouseover and mousemove first; if anything is changed during these events, 'click' and related events don't get fired:
mouseenter and mouseleave appear to be included, though they're not specified in the chart.
If you modify anything as a result of these events, click events won't get fired. That includes something higher up in the DOM tree. For example, this will prevent single clicks from working on your website with jQuery:
$(window).on('mousemove', function() {
$('body').attr('rel', Math.random());
});
Edit: For clarification, jQuery's hover event includes mouseenter and mouseleave. These will both prevent click if content is changed.
A better solution, without any JS, css class and viewport check: you can use Interaction Media Features (Media Queries Level 4)
Like this:
#media (hover) {
// properties
my:hover {
color: red;
}
}
iOS Safari supports it
More about:
https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/
The browser feature detection library Modernizer includes a check for touch events.
It’s default behavior is to apply classes to your html element for each feature being detected. You can then use these classes to style your document.
If touch events are not enabled Modernizr can add a class of no-touch:
<html class="no-touch">
And then scope your hover styles with this class:
.no-touch a:hover { /* hover styles here */ }
You can download a custom Modernizr build to include as few or as many feature detections as you need.
Here's an example of some classes that may be applied:
<html class="js no-touch postmessage history multiplebgs
boxshadow opacity cssanimations csscolumns cssgradients
csstransforms csstransitions fontface localstorage sessionstorage
svg inlinesvg no-blobbuilder blob bloburls download formdata">
Some devices (as others have said) have both touch and mouse events. The Microsoft Surface for example has a touch screen, a trackpad AND a stylus which actually raises hover events when it is hovered above the screen.
Any solution that disables :hover based on the presence of 'touch' events will also affect Surface users (and many other similar devices). Many new laptops are touch and will respond to touch events - so disabling hovering is a really bad practice.
This is a bug in Safari, there's absolutely no justification for this terrible behavior. I refuse to sabotage non iOS browsers because of a bug in iOS Safari which has apparently been there for years. I really hope they fix this for iOS8 next week but in the meantime....
My solution:
Some have suggested using Modernizr already, well Modernizr allows you to create your own tests. What I'm basically doing here is 'abstracting' the idea of a browser that supports :hover into a Modernizr test that I can use throughout my code without hardcoding if (iOS) throughout.
Modernizr.addTest('workinghover', function ()
{
// Safari doesn't 'announce' to the world that it behaves badly with :hover
// so we have to check the userAgent
return navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? false : true;
});
Then the css becomes something like this
html.workinghover .rollover:hover
{
// rollover css
}
Only on iOS will this test fail and disable rollover.
The best part of such abstraction is that if I find it breaks on a certain android or if it's fixed in iOS9 then I can just modify the test.
Adding the FastClick library to your page will cause all taps on a mobile device to be turned into click events (regardless of where the user clicks), so it should also fix the hover issue on mobile devices. I edited your fiddle as an example: http://jsfiddle.net/FvACN/8/.
Just include the fastclick.min.js lib on your page, and activate via:
FastClick.attach(document.body);
As a side benefit, it will also remove the annoying 300ms onClick delay that mobile devices suffer from.
There are a couple of minor consequences to using FastClick that may or may not matter for your site:
If you tap somewhere on the page, scroll up, scroll back down, and then release your finger on the exact same position that you initially placed it, FastClick will interpret that as a "click", even though it's obviously not. At least that's how it works in the version of FastClick that I'm currently using (1.0.0). Someone may have fixed the issue since that version.
FastClick removes the ability for someone to "double click".
There are basically three scenarios:
User only has a mouse/pointer device and can activate :hover
User only has a touchscreen, and can not activate :hover elements
User has both a touchscreen and a pointer device
The originally accepted answer works great if only the first two scenarios are possible, where a user has either pointer or touchscreen. This was common when the OP asked the question 4 years ago. Several users have pointed out that Windows 8 and Surface devices are making the third scenario more likely.
The iOS solution to the problem of not being able to hover on touchscreen devices (as detailed by #Zenexer) is clever, but can cause straightforward code to misbehave (as noted by the OP). Disabling hover only for touchscreen devices means that you will still need to code a touchscreen friendly alternative. Detecting when a user has both pointer and touchscreen further muddies the waters (as explained by #Simon_Weaver).
At this point, the safest solution is to avoid using :hover as the only way a user can interact with your website. Hover effects are a good way of indicating that a link or button is actionable, but a user should not be required to hover an element to perform an action on your website.
Re-thinking “hover” functionality with touchscreens in mind has a good discussion about alternative UX approaches. The solutions provided by the answer there include:
Replacing hover menus with direct actions (always visible links)
Replacing on-hover menus with on-tap menus
Moving large amounts of on-hover content into a separate page
Moving forward, this will probably be the best solution for all new projects. The accepted answer is probably the second best solution, but be sure to account for devices that also have pointer devices. Be careful not to eliminate functionality when a device has a touchscreen just to work around iOS's :hover hack.
The JQuery version
in your .css use
.no-touch .my-element:hover
for all your hover rules
include JQuery and the following script
function removeHoverState(){
$("body").removeClass("no-touch");
}
Then in body tag add
class="no-touch" ontouchstart="removeHoverState()"
as soon as the ontouchstart fires the class for all hover states is removed
I agree disabling hover for touch is the way to go.
However, to save yourself the trouble of re-writing your css, just wrap any :hover items in #supports not (-webkit-overflow-scrolling: touch) {}
.hover, .hover-iOS {
display:inline-block;
font-family:arial;
background:red;
color:white;
padding:5px;
}
.hover:hover {
cursor:pointer;
background:green;
}
.hover-iOS {
background:grey;
}
#supports not (-webkit-overflow-scrolling: touch) {
.hover-iOS:hover {
cursor:pointer;
background:blue;
}
}
<input type="text" class="hover" placeholder="Hover over me" />
<input type="text" class="hover-iOS" placeholder="Hover over me (iOS)" />
Instead of only having hover effects when touch is not available I created a system for handling touch events and that has solved the problem for me. First, I defined an object for testing for "tap" (equivalent to "click") events.
touchTester =
{
touchStarted: false
,moveLimit: 5
,moveCount: null
,isSupported: 'ontouchend' in document
,isTap: function(event)
{
if (!this.isSupported) {
return true;
}
switch (event.originalEvent.type) {
case 'touchstart':
this.touchStarted = true;
this.moveCount = 0;
return false;
case 'touchmove':
this.moveCount++;
this.touchStarted = (this.moveCount <= this.moveLimit);
return false;
case 'touchend':
var isTap = this.touchStarted;
this.touchStarted = false;
return isTap;
default:
return true;
}
}
};
Then, in my event handler I do something like the following:
$('#nav').on('click touchstart touchmove touchend', 'ul > li > a'
,function handleClick(event) {
if (!touchTester.isTap(event)) {
return true;
}
// touch was click or touch equivalent
// nromal handling goes here.
});
Thanks #Morgan Cheng for the answer, however I've slightly modified the JS function for getting the "touchstart" (code taken from #Timothy Perez answer), though, you need jQuery 1.7+ for this
$(document).on({ 'touchstart' : function(){
//do whatever you want here
} });
Given the response provided by Zenexer, a pattern that requires no additional HTML tags is:
jQuery('a').on('mouseover', function(event) {
event.preventDefault();
// Show and hide your drop down nav or other elem
});
jQuery('a').on('click', function(event) {
if (jQuery(event.target).children('.dropdown').is(':visible') {
// Hide your dropdown nav here to unstick
}
});
This method fires off the mouseover first, the click second.
For those with common use case of disabling :hover events on iOS Safari, the simplest way is to use a min-width media query for your :hover events which stays above the screen width of the devices you are avoiding. Example:
#media only screen and (min-width: 1024px) {
.my-div:hover { // will only work on devices larger than iOS touch-enabled devices. Will still work on touch-enabled PCs etc.
background-color: red;
}
}
For someone still looking for a solution if none of the above worked,
Try this,
#media (hover: hover)
{
.Link:hover
{
color:#00d8fe;
}
}
This hover pseudo will only be applied for devices with pointers and works normal on touch devices with just .active classes.
Just look at the screen size....
#media (min-width: 550px) {
.menu ul li:hover > ul {
display: block;
}
}
heres the code you'll want to place it in
// a function to parse the user agent string; useful for
// detecting lots of browsers, not just the iPad.
function checkUserAgent(vs) {
var pattern = new RegExp(vs, 'i');
return !!pattern.test(navigator.userAgent);
}
if ( checkUserAgent('iPad') ) {
// iPad specific stuff here
}

Resources