📜 ⬆️ ⬇️

How we: hover on iOS won ...

It's not a secret for anyone, I think, that touch-devices handle mouse events a little differently, not the way it happens on desktop browsers ...

The most vivid example for me is the processing of a pseudo-class :hover . For starters, iOS7, for example, will not respond to hover unless the element, or its parent, has clicked event handling. This is clearly seen here in this example: jsfiddle.net/H8EmG - you don’t see any underscores with your finger on the text. In this example, jsfiddle.net/H8EmG/1 “poking” with your finger in the text will lead to its underlining. An interesting fact - until we push another element, the text will sit under the hover ...

Another interesting example is handling hovering elements: jsfiddle.net/ASRm9/1 Try clicking on the text. First you will see the text "HOVER!", Which appeared inside the line, but the second click will call alert('click') . This is because iOS understands that for :hover something is hidden, and tries not to break the behavior laid down by the author of the site.
')
But once we encountered such a bug, which we could not explain until now, and its localization required more than one debugging day on the iPad ... Those who wanted details, as well as a tricky, I think, solution of probably all problems with: hover at once - I ask under the cat ...



SUDDENLY, after the next update of the service, the developer of the “platform” of which I am, there was an unpleasant problem - on the iPad you cannot select a single line in almost all the “tables” that are on the service. "Click" just does not work! It should be noted that the "table" is not just lines and columns. In our case, this is a fairly “rich” UI element with note marks, sorting, grouping, filters, all sorts of “ladders” for printing and exporting to PDF and Excel ...

After a long and tedious localization of the problem, we identified an isolated, simple piece of HTML + CSS that gave a similar result ...



Here is an example: jsfiddle.net/822eG/4 . Try clicking on the rows of the table. Hover will work (you will see the "checkbox"), but click (and alert ) you will not see how not to try to make a line.

On this topic, I even started a post on SO at stackoverflow.com/questions/21786375/ios-7-hover-click-issue-no-click-triggered-in-some-cases which did not bring much profit, except the proposal to include (it is not clear why ) -webkit-overflow-scrolling: touch on the container of the label that really helped with the example from jsFiddle, but did not help with the real application.

In the process of thinking about this mess, the following decision came (my own answer to the question on SO) - and what if :hover replaced with a CSS class that “throws” in code, catching mouseenter / mouseleave ? This simple fix actually solves everything. Even begins to work "more fun" - no need to click twice. From the first click, we get both alert and checkbox: jsfiddle.net/822eG/10

For lack of a better option, they began to think about this ... In fact, we have a very large code base. There are a lot of both “platform” code and “application” code based on this platform. And who knows who, where and when, under what conditions he wants to use: hover and whether he wants to hide or show something. In general, it is necessary to have “everything by itself (c)” and the average developer did not think about problems on iOS.

The result was the following solution:



Fortunately, it turned out to be quite simple ... Each styleSheet contains a collection of rules , in which the rules themselves lie. Each rule has the property selectorText which can be changed on the go. And also has a collection of style where firstly contains a set of properties specified in this style - they are stored as an "array". The style has .length , going through the length we get all the properties that are changed in this style. Secondly, the style contains the values ​​of the changed properties. At the index equal to the name of the property the value of the property is stored.

That is, if we have, say, a CSS code:

 .myClass:hover .block, .myItem:hover .element { color: red; display: block; } 


then this rule has selectorText == '.myClass:hover .block, .myItem:hover .element' , style.length == 2 , style[0] == 'color' , style[1] == 'display' , style.color == 'red' and style.display == 'block' .

Everything else is a matter of technology ...

Unfortunately, it turned out that the primary bypass of rules works (on our volumes of styles and link tags) not very quickly ... Profiling showed that turning to rules takes the lion's share of time. Perhaps WebKit initializes this property lazily and the first call initiates some deep parsing of styles into a set of objects.

Here's what happened:

 $(document).ready(function(){ // ,    ,     if (!$ws._const.browser.isMobileSafari) { return; } var $body = $('body'); //     function addPseudoHover() { this.classList.add('ws-pseudo-hover'); } //     "" function removePseudoHover() { this.classList.remove('ws-pseudo-hover'); } //   [].filter(...) function uniq(item, index, array) { return array.indexOf(item, index + 1) == -1; } function trimHoverBase(selector) { return selector.substr(0, selector.indexOf(':hover')).trim(); } function filterHoverSelectors(selector) { return selector.indexOf(':hover') != -1; } function createBodyDelegate(hoverSelector){ $body.delegate(hoverSelector, 'mouseenter', addPseudoHover); $body.delegate(hoverSelector, 'mouseleave', removePseudoHover); } function processMutationRecord(mutationRecord) { var needRefresh = false; if (mutationRecord.addedNodes) { for(var i = 0, l = mutationRecord.addedNodes.length; i < l; i++) { if (mutationRecord.addedNodes[i].nodeName == 'LINK') { needRefresh = true; break; } } } if (needRefresh) { checkStylesheetSetDebonuced(); //       } } function checkStylesheetSet() { var allHoverSelectors = [], allRules = [], sheet, sheetCheckResult; for(var i = 0, l = document.styleSheets.length; i < l; i++) { sheet = document.styleSheets[i]; // ,      if (sheet.processed || sheet.rules.length === 0) { continue; } sheetCheckResult = checkCSSRuleSet(sheet); if (sheetCheckResult.rules.length > 0 && sheetCheckResult.selectors.length > 0) { Array.prototype.push.apply(allHoverSelectors, sheetCheckResult.selectors); Array.prototype.push.apply(allRules, sheetCheckResult.rules); } //           sheet.processed = true; } //   allRules.forEach(function(aRule){ aRule.selectorText = aRule.selectorText.replace(':hover', '.ws-pseudo-hover'); }); //   ,    body allHoverSelectors.map(trimHoverBase).filter(uniq).forEach(createBodyDelegate); } var checkStylesheetSetDebonuced = checkStylesheetSet.debounce(420); function checkCSSRuleSet(sheet) { var result = { selectors: [], rules: [] }; for(var i = 0, l = sheet.rules.length; i < l; i++) { var rule = sheet.rules[i]; if (rule.styleSheet && rule.href /* instanceof CSSImportRule*/) { //    @import checkCSSRuleSet(rule.styleSheet); } else if (rule.selectorText /* instanceof CSSStyleRule*/) { var hoverSelectors = getHoverSelectors(rule); if (hoverSelectors.length > 0) { if (checkStyles(rule)) { Array.prototype.push.apply(result.selectors, hoverSelectors); result.rules.push(rule); } } } } return result; } function checkStyles(rule) { for(var i = 0, l = rule.style.length; i < l; i++) { var styleItem = rule.style[i]; if (styleItem == 'display' && rule.style.display !== 'none') { return true; } if (styleItem == 'visibility' && rule.style.visibility !== 'hidden') { return true; } } return false; } function getHoverSelectors(rule) { return rule.selectorText.split(',').filter(filterHoverSelectors); } //      head new MutationObserver(function(mutationRecords){ mutationRecords.forEach(processMutationRecord); }).observe(document.getElementsByTagName('head')[0], { childList: true }); }); 


A few links:

Source: https://habr.com/ru/post/212959/


All Articles