блестящий интерактивный сюжет со смартфона не понимает движений пальцев

У меня есть приложение R-Shiny с графиком, который реализует интерактивные действия: щелчок, зависание (зависание - это наведение мыши на график, который можно обнаружить по блеску). Чтобы дать идею, я выкладываю ниже упрощенный shinyapp с функциональностью, которая проблематична для меня, интерактивный сюжет для рисования. (это взято из моего старого ответа здесь)

Это на самом деле работает нормально, однако мне нужно, чтобы люди использовали его со своих смартфонов. Проблема: движения пальцев, которые мы делаем в смартфоне, интерпретируются телефоном как увеличение страницы или прокрутка страницы, а не как выбор мыши или движение мыши по сюжету (зависание).

Есть ли какая-либо модификация кода (java? Css?), Которую я могу реализовать в приложении, чтобы превратить сенсорные события в события мыши, или параметр/жест на смартфоне для включения движения, подобного мышке?

Большое спасибо. Код:

library(shiny)
ui <- fluidPage(
  h4("Click on plot to start drawing, click again to pause"),
  sliderInput("mywidth", "width of the pencil", min=1, max=30, step=1, value=10),
  actionButton("reset", "reset"),
  plotOutput("plot", width = "500px", height = "500px",
             hover=hoverOpts(id = "hover", delay = 100, delayType = "throttle", clip = TRUE, nullOutside = TRUE),
             click="click"))
server <- function(input, output, session) {
  vals = reactiveValues(x=NULL, y=NULL)
  draw = reactiveVal(FALSE)
  observeEvent(input$click, handlerExpr = {
    temp <- draw(); draw(!temp)
    if(!draw()) {
      vals$x <- c(vals$x, NA)
      vals$y <- c(vals$y, NA)
    }})
  observeEvent(input$reset, handlerExpr = {
    vals$x <- NULL; vals$y <- NULL
  })
  observeEvent(input$hover, {
    if (draw()) {
      vals$x <- c(vals$x, input$hover$x)
      vals$y <- c(vals$y, input$hover$y)
    }})
  output$plot= renderPlot({
    plot(x=vals$x, y=vals$y, xlim=c(0, 28), ylim=c(0, 28), ylab="y", xlab="x", type="l", lwd=input$mywidth)
  })}
shinyApp(ui, server)

Ответы

Ответ 1

Вы можете отключить жесты панорамирования и масштабирования на графике, используя свойство touch-action CSS:

#plot {
  touch-action: none;
}

Превратить события касания в события мыши немного сложнее, но вы можете прослушивать события касания, такие как touchstart, touchmove, touchend и моделировать эквивалентные события мыши в JavaScript. См. https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Using_Touch_Events и https://javascript.info/dispatch-events для получения дополнительной информации.

Это не идеально, но я дал ему шанс. Я отключил сенсорные жесты на графике и добавил скрипт, который преобразует touchmove в mousemove и сообщает серверу, когда начинать рисовать (на touchstart) и прекращать рисовать (на touchend).

library(shiny)

ui <- fluidPage(
  h4("Click on plot to start drawing, click again to pause"),
  sliderInput("mywidth", "width of the pencil", min=1, max=30, step=1, value=10),
  actionButton("reset", "reset"),
  plotOutput("plot", width = "400px", height = "400px",
             hover=hoverOpts(id = "hover", delay = 100, delayType = "throttle", clip = TRUE, nullOutside = TRUE),
             click="click"),
  tags$head(
    tags$script("
      $(document).ready(function() {
        var plot = document.getElementById('plot')

        plot.addEventListener('touchmove', function (e) {
          var touch = e.changedTouches[0];
          var mouseEvent = new MouseEvent('mousemove', {
            view: window,
            bubbles: true,
            cancelable: true,
            screenX: touch.screenX,
            screenY: touch.screenY,
            clientX: touch.clientX,
            clientY: touch.clientY
          })
          touch.target.dispatchEvent(mouseEvent);
          e.preventDefault()
        }, { passive: false });

        plot.addEventListener('touchstart', function(e) {
          Shiny.onInputChange('draw', true)
          e.preventDefault()
        }, { passive: false });

        plot.addEventListener('touchend', function(e) {
          Shiny.onInputChange('draw', false)
          e.preventDefault()
        }, { passive: false });
      })
    "),
    tags$style("#plot { touch-action: none; }")
    )
)

server <- function(input, output, session) {
  vals = reactiveValues(x=NULL, y=NULL)
  draw = reactiveVal(FALSE)

  observeEvent(input$click, {
    draw(!draw())
    vals$x <- append(vals$x, NA)
    vals$y <- append(vals$y, NA)
  })

  observeEvent(input$draw, {
    draw(input$draw)
    vals$x <- append(vals$x, NA)
    vals$y <- append(vals$y, NA)
  })

  observeEvent(input$reset, handlerExpr = {
    vals$x <- NULL; vals$y <- NULL
  })

  observeEvent(input$hover, {
    if (draw()) {
      vals$x <- c(vals$x, input$hover$x)
      vals$y <- c(vals$y, input$hover$y)
    }
  })

  output$plot= renderPlot({
    plot(x=vals$x, y=vals$y, xlim=c(0, 28), ylim=c(0, 28), ylab="y", xlab="x", type="l", lwd=input$mywidth)
  })
}

shinyApp(ui, server)

Ответ 2

Хотя я не могу полностью решить вопрос, возможно, грязный обходной путь также может иметь какую-то ценность для вас. Или кто-то еще может опираться на этот ответ.

Я мог воспроизвести ошибку, что щелчки на графике не фиксируются на мобильном телефоне. Но я заметил, что могу добавить дополнительные кликвенты с помощью javascript/smoothjs.

Одним из способов будет:

  onevent(event = "click", id = "plot", function(e){
    global$clickx = c(global$clickx, e$pageX - 88)
    global$clicky = c(global$clicky, 540 - e$pageY)
  })

У него есть несколько недостатков:

  • фигуры рисуются только линиями, это не захватывает все зависание на графике
  • позиция довольно неточная, так как вы должны учитывать границы и поля (очень грязные, но потенциальные здесь)

Через несколько часов у меня немного вышло время, наверняка можно его улучшить, но, может быть, оно все равно вас заинтересует.

Проверьте здесь: (ссылка может измениться в течение следующих недель)

http://ec2-3-121-215-255.eu-central-1.compute.amazonaws.com/shiny/rstudio/sample-apps/mobile/

Воспроизводимый код: (проверено на смартфоне: Mi A2)

library(shiny)
library(shinyjs)
ui <- fluidPage(
  useShinyjs(),
  h4("Click on plot to start drawing, click again to pause"),

  plotOutput(outputId = "plot", width = "500px", height = "500px")
)


server <- function(input, output, session) {

  onevent(event = "click", id = "plot", function(e){
    global$clickx = c(global$clickx, e$pageX - 88)
    global$clicky = c(global$clicky, 540 - e$pageY)
  })

  global <- reactiveValues(clickx = NULL, clicky = NULL)

  output$plot= renderPlot({
    plot(x = NULL, y = NULL, xlim=c(0, 440), ylim=c(0, 440), ylab="y", xlab="x", type="l")
    len <- length(global$clickx)
    lines(x = global$clickx, y = global$clicky, type = "p")      
    if(len > 1){
      for(nr in 2:len){
        lines(x = global$clickx[(nr - 1):nr], y = global$clicky[(nr - 1):nr], type = "l")
      }
    }
  })
}
shinyApp(ui, server)