7

I'm trying to to implement touch scroll in less extension to jQuery Terminal. It work similar to less unix command.

I have this code:

self.touch_scroll(function(event) {
    // how much difference changed since last touch move
    var delta = event.current.clientY - event.previous.clientY;
    var ret;
    var interpreter = interpreters.top();
    if (is_function(interpreter.touchscroll)) {
        ret = interpreter.touchscroll(event, delta, self);
    } else if (is_function(settings.touchscroll)) {
        ret = settings.touchscroll(event, delta, self);
    }
    if (ret === true) {
        return;
    }
    return false;
});
// make_callback_plugin is helper that use $.Callbacks and make sure that there is only
// one handler on the element
$.fn.touch_scroll = make_callback_plugin({
    name: 'touch',
    init: function(handler) {
        var origin;
        var previous;
        $(this).on('touchstart.scroll', function(e) {
            e = e.originalEvent;
            if (e.touches.length === 1) {
                previous = origin = e.touches[0];
            }
        }).on('touchmove.scroll', function(e) {
            e = e.originalEvent;
            console.log(!!origin + ' && ' + (e.touches.length) + ' === 1');
            if (origin && e.touches.length === 1) {
                var current = e.touches[0];
                var ret = handler({
                    origin: origin,
                    previous: previous,
                    current: current
                });
                if (ret === false) {
                    // this don't change anything
                    e.preventDefault();
                }
                previous = current;
            }
        }).on('touchend.scroll', function() {
            if (origin || previous) {
                origin = previous = null;
            }
        });
    },
    destroy: function() {
        $(this).off('touchstart.scroll touchmove.scroll touchend.scroll');
    }
});

and inside less I have:

    function scroll(delta, scroll_by) {
        if (delta > 0) {
            pos -= scroll_by;
            if (pos < 0) {
                pos = 0;
            }
        } else {
            pos += scroll_by;
            if (pos - 1 > lines.length - rows) {
                pos = lines.length - rows + 1;
            }
        }
        print();
        return true;
    }
    term.push($.noop, {
        onResize: refresh_view,
        touchscroll: function(event, delta) {
            console.log({delta});
            var offset = Math.abs(delta);
            // 14 is default height of single line in pixels
            scroll(delta, Math.ceil(offset / 14));
            return false;
        },
        mousewheel: function(event, delta) {
            return scroll(delta, scroll_by);
        }, 

I also have this css:

.terminal-less {
    touch-action: none;
    overscroll-behavior-y: contain;
}

on Mousewheel scrolling works good it scroll with the same amount of scroll_by which is by default 3 (seems about right). (pos is lines offset so if I use pos++ it move/scroll by one line, delta in touchscroll is positive or negative from about -20 to 20 pixels.

The problem I have and the question is, how can I make it scroll with the finger? it don't feel right. Also it scroll only once it don't move with the finger. touchmove fire only once, shoudn't it fire while I move the finger while touching the phone?

Anyone have experience with this type of touch scroll behavior?

I was searching for similar problem and didn't found solution. Do you know why touchmove could fire once? The only thing I can think of was textarea that is used as clipboard (on mobile it's also used to enable virtual keyboard), but I've set background to red and it don't move on Android. I was testing other code from this drawing demo:

https://zipso.net/a-simple-touchscreen-sketchpad-using-javascript-and-html5/

and it works fine, touch move keeps firing while you move the finger.

Any ideas? It will be hard to replicate but if somone is interested in investigation I can put all my code on github in jQuery Terminal repo (in some branch).

What's weird is that touchend don't fire after touchmove, it fire once only when I click on the terminal to enable keyboard.

I've tried to monkey patch jQuery on and log each time it fire but I didn't have any other event (that may prevent default behavior) also according to docs mouse events fire after touchend and those don't fire.

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • It may or may not be related, but there's a bug in mobile safari where scroll events wont trigger after swiping and releasing. I don't know much more about it, but I saw it mentioned in the intro of this post: https://destroytoday.com/blog/casper-homepage-section – James Tomasino Mar 02 '20 at 14:32
  • @JamesTomasino I'm testing on Android. – jcubic Mar 02 '20 at 16:06
  • @jcubic "if someone is interested in investigation I can put all my code on Github in jQuery Terminal repo": consider me being interested (a jsfiddle / StackOverflow snippet with only the related code [but as an executable standalone sample] would be prefered though) – Fabian N. Mar 04 '20 at 18:14

1 Answers1

5

What you need is simple .terminal-wrapper { pointer-events: none; } (based on the devel branch). But with this rule you can't select the text, that's why you need to use it only for mobile devices.

I'm not sure if this will block the selection of text on mobile, but if so, you can try to add this on touchstart (or even on touchmove as the first instruction) and remove it on touchend.

Also, I had to add some JS code, because without it interpreter.touchScroll is undefined. But this is not the main cause of the problem.

interpreters = new Stack($.extend({}, settings.extra, {
    name: settings.name,
    prompt: prompt,
    keypress: settings.keypress,
    keydown: settings.keydown,
    resize: settings.onResize,
    greetings: settings.greetings,
    mousewheel: settings.mousewheel,
    touchScroll: settings.touchScroll, // NEW LINE
    history: settings.history,
    keymap: new_keymap
}, interpreter));
self.touch_scroll(function(event) {
    var delta = event.current.clientY - event.previous.clientY;
    var ret;
    var interpreter = interpreters.top(); // NEW LINE
    if (is_function(interpreter.touchScroll)) {
        ret = interpreter.touchScroll(event, delta, self);
    } else if (is_function(settings.touchScroll)) {
        ret = settings.touchScroll(event, delta, self);
    }
    if (ret === true) {
        return;
    }
});

Without pointer-events: none;

enter image description here

With pointer-events: none;

enter image description here

artanik
  • 2,599
  • 15
  • 24
  • 1
    Thanks for your answer will check it when I'll be back home. If it will fix my issue I'll create another bounty and give you the points. I can't award the bounty right now, because I can't test your solution. But it looks very promising. – jcubic Mar 08 '20 at 06:14
  • 1
    Finally tested your solution, it's perfect. Also holding finger to select text works perfectly without the need to toogle pointer-events. The last comment about another bounty was becasue I've thought that it will expire before I will be able to test the solution. – jcubic Mar 10 '20 at 09:49
  • There is one problem I need 3 mobile features that I need to merge in one library, focus on click/touch (using contenteditable), touchscroll and click on links. I was able to make it work with only 2 of them at the time. If I use pointer-events I can't click on links. And I'm not able to trigger adding pointer-events in touchstart it need to be added before touchstart happen. Do you have any other solutions? – jcubic Mar 15 '20 at 17:30
  • You can use `pointer-events: visible;` on links, it should make them "clickable", example: https://codesandbox.io/s/frosty-sun-zrxxq _(Black buttons have that property and log text to console while gray doesn't)_ – artanik Mar 15 '20 at 17:44
  • Awesome, thank you, this works 100% like I want. I've thought that pointer-events works like opacity and you can't enable them on children. – jcubic Mar 16 '20 at 08:33