Skip to content

Instantly share code, notes, and snippets.

@pixelthing
Last active March 28, 2019 16:39
Show Gist options
  • Save pixelthing/0fd0603eb1a89e82ea90bd8b1ae09676 to your computer and use it in GitHub Desktop.
Save pixelthing/0fd0603eb1a89e82ea90bd8b1ae09676 to your computer and use it in GitHub Desktop.
Making codepen.io a little more iPad friendly. @pixelthing
/*
Making codepen.io a little more iPad friendly. @pixelthing
First - I hope this doesn't come across as being a bit of an dick. I massively appreciate
CodePen and know the huge amount of pressure that comes from every direction in a project this
big, so totally understand that not every user experience can be addressed with the time and
resources available. Think of this as a pull request for your consideration.
*Do not feel the need to pity-implement anything or let me down gently. It's your site, not mine.*
This is a shim to add to the CodePen editor styles (eg, greasemonkey style).
It's a pure CSS stylesheet (with a breakpoint to scope it away from desktop browsers)
to improve the user experience specifically on iPad. I haven't done much testing on iPhone,
but would think another 30 minutes could squelch most weirdness there too. It's intended
as much as possible to be a fire and forget solution (not too much hacky stuff, scoped
to the problem that need fixing), but of course I'd advise thorough device testing!
Still, I'd love to see codepen be a rock solid tool on iPad/iPad Pro, as the platform is changing rapidly.
To test and see it in action (without deploying), connect an iPad to a Mac and open Mac Safari. If Safari on your
iPad has devtools turned on in the system settings, it'll appear as a remote device in the Mac Safari "Develop"
menu. Selecing the device will bring up devtools and you can go to the "Resources" tab, find any random
Stylesheet, and paste this CSS in. Voila! The shim is now running on your iPad - at least until you refresh the page...
The problems it tries to solve are:
- the whole browser document trying to scroll all the time, especially with touch scrolling in the main output area and dragging the panel resizers. (annoyance level: 9)
- having larger touch targets to resize editor panels (annoyance level: 3)
- able to successfully resizing the editor panels via drag and double-click (annoyance level: 9). only partly fixed (see below)
- adding feedback and affordance when you are successfully manipulating the panel resizers. (annoyance level: 7)
- disabling the screen zooming when you intend to double click collpase/expand a panel. (annoyance level: 8)
- disabling text-selection loupes and webtap highlight colours and when you intended to resize panels (annoyance level: 4)
Still unfixed:
- using editor resizers in iOS is still buggy - but they works pretty well in layout-top mode. The same thing happens for the main area resizer when that is in layout-top mode (ie, in the same orientation). At least the double-click expand/collapse works well in layout-side mode now it doesn't zoom the screen.
- very occasionally you get a main thread lock. It will appear as a panel not scrolling (or appearing to scroll in a non-momentum way), or resizers not appearing to be selected. It could be garbage collection, or a setTimeout on something, or a localstorage sync connection, or messaging a web worker? Either way, it's tough to diagnose, but seems ot lock out touch interaction more often than I see on a desktop (despite my iPad being measurably fast in almost all JS functions than my MacBook). Maybe it's just because I have Safari devtools open (I have to plug the iPad into the Mac to test these CSS changes).
- double-clicking a panel (bare with me...) to expand it when it's collapsed/skinny in layout-top doesn't work. Not sure if this is a general bug (will try on desktop when the site comes back online after today's outage)
- When the JS panel is collapsed in layout-top mode (ie, when it's collapsed top-right), it's devilishly difficult to drag out on iOS as it more often triggers the "swipe from right of screen" gesture associated with pulling the multi-tasking pop-over window on-screen. I'd move it in 10px from the right to give better affordance, but as the positioning is set in JS, so I can't fix it in pure CSS.
- repeatedly dragging the resizers with touch gives strange intermittent jumping to different positions. My suspicion is that it's setting a variable somewhere for the last position, but only on touchEnd/dragEnd, and if touchCancel fires instead (as it often does), the variable is not changed and is actually the *previously* set position, not the one you wanted. Just a guess.
Notes:
- this is only in the "twilight" theme at the moment
- tested on an iPad Pro 10.5" 2017 with iOS12.1.4 (and a slightly cracked screen, dammit).
*/
/*
using @media pointer as a "touch device" scoped breakpoint.
ie "use below styles ONLY IF primary pointing device is a finger".
I know there's lots to discuss about the "rightness" of this
solution, but having used this form several years and so having
grandfathered out most touch devices that no longer support it, plus
the sunlit uplands of combined finger/mouse browsers largely in the
past, I'd say that pragmatically - it just works in almost every case.
*/
@media ( pointer: coarse ) {
/* rules for the whole enviroment */
body {
position : fixed; /* feels 20% dangerous because, well - fixed - weird stuff happens in composition and stacking. But the problem this solves is sooooo fundamental to the UX on iPad, it might be worth it. without it, every other goddamned swipe up/down (eg, dragging, scrolling) scrolls the whole window before you get the result you expected. [sigh]. But with this. the outer window no longer scrolls - whoopee! It *might be better to do this via JS (touchStart on body is preventDefault, then enable them again further down the DOM) because I'm not sure the scroll event isn't still being hijacked by document (ie, touch scrolling in panels is still intermittently buggy - suspicious!) * */
width : 100%; /* only here to compliment the postion:fixed hack above */
touch-action : manipulation; /* disables double-tap to zoom (but NOT pinch zoom) - especially using for double-tapping resizers, which pretty frustratingly zoom the whole screen. Weirdly, this doesn't work in layout-top mode if you click on the '.powers', and only the '.powers' - and I have no idea why. */
}
.code-wrap {
-webkit-tap-highlight-color: rgba(0,0,0,0); /* occasionally you try and drag a resizer and the browser thinks you are focusing on the code-wrap above it. This removes the 300ms touch delay to it, giving a better finger-response to actually hitting the re-sizer. Or maybe I'm just imagining it... */
}
/* resizers */
.resizer,
.editor-resizer {
position : relative; /* only here to enable the drag affordance bars below */
-webkit-user-select : none; /* I *hate* seeing the "loupe" maginfier pop up when I'm not trying to select text. Plus it slows down any interaction as the UI tries to determine if it needs to fire an event or to select a block of text (is there a 300ms delay in here? it feels like it) */
user-select : none; /* prefix-shambles! */
-webkit-tap-highlight-color : rgba(0,0,0,0); /* when you tap items in iOS, you you get a brief feedback colour change. I'm not totally sure if it gives a delay to a touch event, or if it's just a perception, but I don't want it anyway, especially if we add our own feedback. */
transition : background-color 300ms; /* (fig 2) because I love asymetrical transition durations, it looks nice without killing that instant feedback */
transition-delay : 300ms; /* (fig 1) two reasons: when double clicking the resizer to toggle it you would get the resizer lighting up twice (a little ugh), plus I quite often add this "asymettric" delay into touch devices because your finger sometimes obscures the intended transition, so you lose the affordance it gives. */
}
body.layout-side .css-editor-resizer,
body.layout-side .js-editor-resizer,
body.layout-top .editor-resizer-console {
height : 14px; /* I'm, loving the thicker resizer in the main window - so much easier on a touch device! So this echos it in the editor resizers. Of course this means less room on screen for editing, but maybe it's a half step to a better solution for touch (maybe touch and hold the whole ".powers" bar to act as resizer? Then the resizer CSS can shrink back down again). Note that we *DON'T* add the extra height to the top resizer - in layout-side mode it serves no UI purpose so just steals pixels. */
}
.powers {
-webkit-user-select : none; /* as above */
user-select : none; /* as above */
-webkit-tap-highlight-color : rgba(0,0,0,0); /* as above */
}
body.layout-top .html-editor-resizer:not(.is-horiz-skinny) {
width : 0; /* just because we don't need the first resizer taking up valuable space in horizontal mode (this is true for non-touch devices too), at least until it's collapsed in "skinny mode" when it *does* serve a purpose... */
}
body.layout-top .html-editor-resizer {
transition : width 300ms 0ms, background-color 300ms 300ms; /* ...and this just makes that collapse between skinny mode and regular a tiny bit nicer. Worth testing against the installed user base as it's transforming a non-composition level rule. It looks 30fps minimum on my iPad Pro 2017, but might steal CPU cycles on older android tablets (but check against the user data, that might not be a segment that services a lot of codepen's users - cheaper chromebooks might be a better target to check against, as they're more likely to match a user scenario like class students. In fact - do Chromebooks register as @media (pointer:coarse) ?? ) */
}
/* resizer drag affordance (two bars as CSS pseudo elements - not strictly necessary - purely design/UX aid - as such, this is just an opinion I'm willing to see ditched at your designers whim!!) */
.resizer:before,
.resizer:after,
.editor-resizer:not(.html-editor-resizer):before,
.editor-resizer:not(.html-editor-resizer):after,
.editor-resizer-console:before,
.editor-resizer-console:after {
content : '';
position : absolute;
top : 50%;
left : 50%;
transition : background-color 300ms; /* as above */
transition-delay : 300ms; /* as above */
background-color : #333;
}
body.layout-top .resizer:before,
body.layout-top .resizer:after,
body.layout-side .editor-resizer:before,
body.layout-side .editor-resizer:after,
.editor-resizer-console:before,
.editor-resizer-console:after {
width : 40px;
height : 2px;
margin : -3px 0 0 -20px;
}
body.layout-top .resizer:after,
body.layout-side .editor-resizer:after,
.editor-resizer-console:after {
margin-top : 2px;
}
body.layout-side .resizer:before,
body.layout-side .resizer:after,
body.layout-top .editor-resizer:before,
body.layout-top .editor-resizer:after {
width : 2px;
height : 40px;
margin : -20px 0 0 -3px;
}
body.layout-side .resizer:after,
body.layout-top .editor-resizer:after {
margin-left : 2px;
}
body.layout-top .resizer:before,
body.layout-top .resizer:after,
body.layout-top .editor-resizer:before,
body.layout-top .editor-resizer:after {
background-color : #000; /* never noticed that the resizers are a different colour in layout-top! */
}
/* resizers: finger-down affordance/behaviour */
body.editor .resizer:active,
body.layout-top .html-editor-resizer.is-horiz-skinny:active, /* note the finger-down affordance for the HTML resizer only occurs for layout-top, when it's in skinny mode. The rest get it all the time */
body.editor .top-boxes .css-editor-resizer:active,
body.editor .top-boxes .js-editor-resizer:active,
body.editor .top-boxes .editor-resizer-console:active {
background-color : #fc0; /* whatever ative colour you want (*probably not this yellow!*), but the feedback that you're successfully touching the resizer is invaluable with a 40px wide pointer like your stubby finger */
transition : background-color 50ms; /* because I love asymetrical transition durations as mentioned in (fig 2), it looks nice without disturbing the response */
transition-delay : 0ms; /* the second part of the asymetric delay mentioned in (fig 1). returns to "instand" feedback on touch */
}
body.layout-top .editor-resizer .box-title {
transition : color 300ms 300ms; /* asymetrical timing of active/inactive state to match the main resizer (I hate it when effort is put into one element and the rest are forgotten...) */
}
body.layout-top .editor-resizer:active .box-title {
color : #000; /* changes the color of the HTML/CSS/JS labels in the resizers in skinny mode when finger down (not just pretty, increases readability and contrast when you finger is down) */
transition : color 50ms 0ms;
}
body.editor .resizer:active:before,
body.editor .resizer:active:after,
.css-editor-resizer:active:before,
.css-editor-resizer:active:after,
.js-editor-resizer:not(:first-child):active:before,
.js-editor-resizer:not(:first-child):active:after,
.editor-resizer-console:active:before,
.editor-resizer-console:active:after {
background-color : #c63; /* the finger-down colour of the resize UI. I don't sanction this colour combination! */
transition : background-color 50ms; /* as above */
transition-delay : 0ms; /* as above */
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment