mirror of
https://github.com/discourse/discourse.git
synced 2024-12-18 15:53:55 +08:00
d208396c5c
PERF: improve touch, swipe, panning performance on mobile menus --- * stop event propagation on swipe events: other touch events were stealing a huge amount of time here. Stop event propagation when handling pan events. * animate with [web animations api](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) * prefer translate3d to hint for gpu rendering. * query document for elements only on start move event, not on subsequent move events * remove unused calculations for directioned velocity and distance: all swipe/pan elements function in x/y direction only. * re-implement scroll locking behavior. re-implemented scroll lock behavior --- With stop event propagation, we need to re-implement scroll locking on menu swipes. Previously, this was using onTouchMove which was costly. We may now use styling with overflow-y:hidden to lock scroll behavior. overflow:hidden on html/body elements is now supported by iOS as of 2022 https://bugs.webkit.org/show_bug.cgi?id=153852 https://bugs.webkit.org/show_bug.cgi?id=220908 UX: improve swipe --- Some improvements to get gestures and swipes feeling a little more polished. This focuses on end gesture, and how we transfer it to a css animation to complete a menu open/close action. Multitouch: events may pan, scroll, and zoom - especially on iOS safari. Cancelling the swipe event allows for a more pleasant zooming experience. * ease-out on menus opening, linear on close * calculate animation duration for opening and closing, attempt to better transfer user swipe velocity to css animation. * more timely close/open and cleanup from calculated animation timing. * add animation to closing menus on cloak tap * correctly animate menus with ease-in and ease-out * add swipe cancel event on multitouch event DEV --- * lean on promises js animations api gives us promises to listen to. Update test waiters to use waitForPromise from @ember/test-waiters instead of reigster/unregister. * convert swipe mixin to its own class. Convert swipe callbacks to custom events on the element. Move shared functions for max animation time and close logic to new shared class. swipe-events lib uses custom events to trigger callbacks, rather than assuming implemented hard coded function from the mixin's base class. Custom events are triggered from the bound element as swipestart, swipeend, swipe Add shared convenience functions for swipe events so they can be more easily shared. A client receives an initial swipe event and can check some state to see if it wants to handle the swipe event and if it doesn't, calling `event.preventDefault();` will prevent `swipe` and `swipeend` events from firing until another distinct swipestart event is fired. Swipe events will auto-cancel on multitouch. The scroll lock has also exposed as its own utility class.
297 lines
6.6 KiB
SCSS
297 lines
6.6 KiB
SCSS
.timeline-loading {
|
|
width: 900px;
|
|
}
|
|
|
|
.timeline-container {
|
|
box-sizing: border-box;
|
|
z-index: z("timeline");
|
|
|
|
&.timeline-docked-bottom {
|
|
.timeline-footer-controls {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
cursor: default;
|
|
}
|
|
}
|
|
|
|
&.timeline-fullscreen.show {
|
|
max-height: 700px;
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
transition: max-height 0.4s ease-out;
|
|
}
|
|
|
|
@media screen and (max-height: 425px) {
|
|
max-height: 75vh;
|
|
}
|
|
.topic-timeline {
|
|
.timeline-footer-controls {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
max-width: calc(100% - 9em); // 9em is the width of the timeline
|
|
}
|
|
}
|
|
.timeline-controls {
|
|
display: table-cell;
|
|
vertical-align: top;
|
|
}
|
|
}
|
|
|
|
&.timeline-fullscreen {
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
transition: max-height 0.3s ease-in;
|
|
}
|
|
max-height: 0;
|
|
position: fixed;
|
|
margin-left: 0;
|
|
background-color: var(--secondary);
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
border-top: 1px solid var(--primary-low);
|
|
box-shadow: var(--shadow-composer);
|
|
padding: 20px 0px;
|
|
z-index: z("fullscreen");
|
|
@media screen and (max-height: 425px) {
|
|
padding-top: 10px;
|
|
}
|
|
.back-button {
|
|
display: none;
|
|
}
|
|
.topic-timeline {
|
|
width: 100%;
|
|
table-layout: fixed;
|
|
margin-left: 0;
|
|
margin-right: 0;
|
|
display: table;
|
|
touch-action: none;
|
|
|
|
.timeline-date-wrapper {
|
|
text-align: right;
|
|
}
|
|
.post-excerpt {
|
|
max-width: 650px;
|
|
line-height: var(--line-height-large);
|
|
display: block;
|
|
@include line-clamp(8);
|
|
@media screen and (max-height: 425px) {
|
|
@include line-clamp(5);
|
|
}
|
|
}
|
|
.username {
|
|
color: var(--primary-high-or-secondary-low);
|
|
word-wrap: break-word;
|
|
font-weight: bold;
|
|
}
|
|
.title {
|
|
margin-top: 0;
|
|
padding-left: 1em;
|
|
display: table-cell;
|
|
vertical-align: top;
|
|
width: 100%;
|
|
h2 {
|
|
margin-top: 0;
|
|
display: block;
|
|
line-height: var(--line-height-medium);
|
|
font-size: var(--font-up-2);
|
|
@include line-clamp(4);
|
|
}
|
|
a {
|
|
color: var(--primary);
|
|
}
|
|
}
|
|
.timeline-last-read {
|
|
right: 0;
|
|
margin-left: 0;
|
|
.progress {
|
|
display: none;
|
|
}
|
|
}
|
|
.timeline-footer-controls {
|
|
display: none;
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 10px;
|
|
|
|
.topic-admin-menu-button-container {
|
|
display: flex;
|
|
}
|
|
|
|
.topic-admin-menu-button {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.timeline-scrollarea-wrapper {
|
|
display: table-cell;
|
|
padding-right: 1.5em;
|
|
width: 7em;
|
|
}
|
|
.timeline-scrollarea {
|
|
border-right: 1px solid var(--tertiary-low-or-tertiary-high);
|
|
border-left: none;
|
|
max-width: 120px;
|
|
|
|
.timeline-scroller {
|
|
position: relative;
|
|
margin-right: -1.5em;
|
|
padding-right: 1.5em;
|
|
justify-content: flex-end;
|
|
.timeline-scroller-content {
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
overflow: hidden;
|
|
text-align: right;
|
|
padding-left: 0;
|
|
padding-right: 1em;
|
|
}
|
|
.timeline-handle {
|
|
float: none;
|
|
width: 11px;
|
|
position: relative;
|
|
right: -6px;
|
|
}
|
|
}
|
|
|
|
.timeline-padding {
|
|
margin-left: 0;
|
|
margin-right: -1em;
|
|
}
|
|
}
|
|
}
|
|
|
|
.timeline-scrollarea-wrapper::after {
|
|
content: "";
|
|
display: table;
|
|
clear: both;
|
|
}
|
|
}
|
|
|
|
.topic-timeline {
|
|
transition: opacity 0.2s ease-in;
|
|
touch-action: none;
|
|
min-width: 6em;
|
|
|
|
.timeline-controls {
|
|
margin-bottom: 1em;
|
|
}
|
|
|
|
.timeline-date-wrapper {
|
|
max-width: 9em;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.timeline-footer-controls {
|
|
// this is absolutely positioned to avoid
|
|
// adding extra height below short topics
|
|
// above the topic-footer-controls
|
|
position: absolute;
|
|
margin-top: 1em;
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
transition: opacity 0.2s ease-in;
|
|
}
|
|
display: flex;
|
|
gap: 0.5em;
|
|
flex-wrap: wrap;
|
|
|
|
button:last-child {
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
|
|
.start-date {
|
|
color: var(--primary-med-or-secondary-med);
|
|
}
|
|
|
|
.timeline-scrollarea {
|
|
margin-top: 0.5em;
|
|
margin-left: 0.5em;
|
|
border-left: 1px solid var(--tertiary-low-or-tertiary-high);
|
|
position: relative;
|
|
transform: translateZ(0);
|
|
}
|
|
|
|
.timeline-padding {
|
|
transition: height 0.15s ease-out;
|
|
cursor: pointer;
|
|
// this element has a click event
|
|
// the negative margin lets this element overlap
|
|
// the scrollarea's vertical track and thus it
|
|
// enables taps on the track to work
|
|
margin-left: -1em;
|
|
.widget-dragging & {
|
|
transition: none;
|
|
}
|
|
.dragging & {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
.timeline-handle {
|
|
border-radius: 0.8em;
|
|
width: 0.35em;
|
|
background-color: var(--tertiary-low-or-tertiary-high);
|
|
height: 100%;
|
|
float: left;
|
|
z-index: z("base") + 1;
|
|
outline: 1px solid transparent;
|
|
margin-left: 0.5px;
|
|
}
|
|
|
|
.timeline-scroller-content {
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
overflow: hidden;
|
|
padding-left: 1em;
|
|
position: absolute; // prevents text length from impacting width
|
|
max-height: 3em; // this hides the date when the count + date would wrap to more than 2 lines
|
|
}
|
|
|
|
.timeline-ago {
|
|
color: var(--primary-med-or-secondary-med);
|
|
max-width: 4.6em;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.timeline-scroller {
|
|
@include unselectable;
|
|
margin-left: -0.22em;
|
|
cursor: ns-resize;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.back-button {
|
|
margin-top: 1em;
|
|
}
|
|
}
|
|
|
|
.timeline-replies {
|
|
overflow: hidden;
|
|
overflow-wrap: break-word;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.timeline-last-read {
|
|
position: absolute;
|
|
margin-left: -0.35em;
|
|
.progress {
|
|
font-size: 0.8em;
|
|
color: var(--tertiary);
|
|
margin-right: 1em;
|
|
}
|
|
z-index: z("base") + 2; // above timeline-handle
|
|
}
|
|
|
|
.back-button {
|
|
min-height: unset;
|
|
padding: 2px 5px;
|
|
}
|
|
|
|
.now-date {
|
|
display: inline-block;
|
|
color: var(--primary-med-or-secondary-med);
|
|
margin-top: 0.5em;
|
|
}
|
|
}
|
|
}
|