在 ClojureScript 中,延迟 `:on-click` 事件并仅在 `:on-double-click` 事件未触发时触发

In ClojureScript, delay `:on-click` event and trigger only if `:on-double-click` event is not triggered

延迟 :on-click 事件以首先查看 :on-double-click 事件是否触发的简单方法是什么?

[:div {:on-click (fn [e]
                   ;; listen to double-click event, within 500ms, 
                   ;; if so on-double-click-fn, 
                   ;; if not, on-click-fn
                  )
       :on-double-click (fn [e] 
                          ;; on-click-fn
                         )}]

谢谢!

第一次尝试:

(defn sleep [timeout]
  (let [maxtime (+ (.getTime (js/Date.)) timeout)]
    (while (< (.getTime (js/Date.)) maxtime))))

[:div {:on-click (fn [e] (sleep 500) (print "single-clicked"))
       :on-double-click (fn [e] (print "double-clicked"))}]

第二次尝试:

(def state (atom {:click-count 0}))

(defn handle-click [e click-fns-map]
  (swap! state update :click-count inc)
  (sleep 500)
  (let [click-count (get @state :click-count)]
    (swap! state assoc :click-count 0)
    (cond
      (= click-count 1) ((:on-single-click click-fns-map) e)
      (> click-count 1) ((:on-double-click click-fns-map) e)))))

[:div 
 {:on-mouse-down 
  (fn [e]
    (handle-click e {:on-single-click #(print "single-click")
                     :on-double-click #(print "double-click")}))}]

 ;;=> "single-click"
 ;;=> "single-click"

编辑:

基于,这是一个抽象,它包装了html 元素args 并为您覆盖了:on-click:on-double-click

(defn ensure-single-double-click 
  [{:keys [on-click on-double-click] :as args}]
  (let [waiting? (atom false)]
    (merge 
     args
     {:on-click (fn [e] 
                  (when (compare-and-set! waiting? false true)
                    (js/setTimeout 
                     (fn [] (when @waiting?
                            (on-click %)
                            (reset! waiting? false)))
                     300)))
      :on-double-click (fn [e] 
                         (reset! waiting? false)
                         (on-double-click %))})))

[:a (ensure-single-double-click
      {:style           {:color "blue"} ;; this works
       :on-click        #(print "single-click")
       :on-double-click #(print "double-click")})
    "test"]

这是一种方法:

(defn slow-link [text single-click-fn double-click-fn]
  (let [waiting? (atom false)]
    [:a {:on-click #(when (compare-and-set! waiting? false true)
                      (js/setTimeout (fn [] (when @waiting?
                                              (single-click-fn %)
                                              (reset! waiting? false)))
                                     500))
         :on-double-click #(do (reset! waiting? false)
                               (double-click-fn %))}
     text]))

[slow-link "Test" #(prn "single-click") #(prn "double-click")]

这将启动一个 JS 计时器,它将在 500 毫秒后执行给定的功能。该函数会在超时时间结束后检查我们是否仍在 waiting? 进行另一次点击,如果是,则执行 single-click-fn。如果我们不是 waiting?,则意味着 double-click 事件已经发生,将 waiting? 重置为 false,并调用 double-click-fn.

:on-click 处理程序使用 compare-and-set 仅在我们尚未处于 waiting? 状态时才采取行动,避免 triple/quadruple 点击等的一些不正当行为.

接近,但 compare-and-set! 并没有保护我免受三次点击(或更多点击!),因为如果说,三次点击发生在 500 毫秒内,waiting?第三次将再次设置为 false 并安排第二次超时。我认为这意味着在技术上,每次奇数点击都会安排一个新的超时。

幸运的是,点击事件带有一个名为 detail, which is set to the number of consecutive clicks. I found it here 的 属性。以下内容应该可以解决 OP 的问题,而无需三次点击:

:on-click
(fn [e]
 ; This prevents the click handler from running
 ; a second, third, or other time.
 (when (-> e .-detail (= 1))
   (reset! waiting? true))
   ; Wait an appropriate time for the double click
   ; to happen...
   (js/setTimeout
     (fn []
       ; If we are still waiting for the double click
       ; to happen, it didn't happen!
       (when @waiting?
         (single-click-fn %)
         (reset! waiting? false)))
     500)))
:on-double-click #(do (reset! waiting? false)
                      (double-click-fn %))

虽然三次点击听起来很奇怪,但它们确实有一个目的:select 整行文本,所以我不希望用户错过这种能力。


其余部分是对那些有兴趣使文本 selection 在监听单击的元素上工作的人的附录。我从 Google 来到这里寻找如何做到这一点,所以也许它对某人有帮助。

我 运行 遇到的一个挑战是我的应用程序规定用户可以执行双击 而无需首先释放第二次单击 ;有点像"one-and-a-half-clicks"。为什么?因为我正在监听跨度上的点击,用户可能会双击 select 整个单词,但随后按住第二次单击并拖动鼠标以 select 其他单词在原来的旁边。问题是双击事件处理程序仅在用户 释放 第二次单击后触发,因此 waiting? 未按时设置为 false

我使用 :on-mouse-down 处理程序解决了这个问题:

:on-click
(fn [e]
 ; This prevents the click handler from running
 ; a second, third, or other time.
 (when (-> e .-detail (= 1))
   (reset! waiting? true))
   ; Wait an appropriate time for the double click
   ; to happen...
   (js/setTimeout
     (fn []
       ; If we are still waiting for the double click
       ; to happen, it didn't happen!
       (when @waiting?
         (single-click-fn %)
         (reset! waiting? false)))
     500)))
:on-double-click #(double-click-fn %)
:on-mouse-down #(reset! waiting? false)

请记住,:on-click:on-double-click 处理程序仅在 释放 时触发(并且处理程序按照按下鼠标、单击、双击),这让 :on-mouse-down 处理程序有机会将 waiting? 设置为 false,如果用户尚未释放鼠标,则需要这样做,因为他不会触发了 :on-double-click 事件处理程序。

请注意,现在您甚至不需要在双击处理程序中将 waiting? 设置为 false,因为在双击处理程序 [=71 时鼠标按下处理程序已经完成了该操作=].

最后,在我的特定应用程序中,碰巧用户可能想要 select 文本而不触发点击处理程序。为此,他将单击一段文本,然后在不松开鼠标的情况下将光标拖动到 select 更多文本。释放光标时,不应触发点击事件。因此,我必须另外跟踪用户是否在放开鼠标之前的任何时候进行了 selection(一种 "half-click")。对于这种情况,我必须向组件的状态添加更多内容(一个名为 selection-made? 的布尔原子和一个名为 selection-handler 的事件处理函数)。这种情况依赖于 selection 的检测,并且由于 selection 是在双击时生成的,因此不再需要检查事件的详细信息 属性 以防止三次或更多次点击.

整个解决方案如下所示(但请记住,这是专门针对文本元素的,因此只是对 OP 要求的内容的补充):

(defn component
  []
  (let [waiting? (r/atom false)
        selection-made? (r/atom false)
        selection-handler
        (fn []
          (println "selection-handler running")
          (when (seq (.. js/document getSelection toString))
            (reset! selection-made? true)))]
    (fn []
      [:div
        ; For debugging
        [:pre {} "waiting? " (str @waiting?)]
        [:pre {} "selection-made? " (str @selection-made?)]
        ; Your clickable element
        [:span
          {:on-click
           (fn [e]
            (println "click handler triggered")
            ; Remove the selection handler in any case because
            ; there is a small chance that the selection handler
            ; was triggered without selecting any text (by
            ; holding down the mouse on the text for a little
            ; while without moving it).
            (.removeEventListener js/document "selectionchange" selection-handler)
            (if @selection-made?
              ; If a selection was made, only perform cleanup.
              (reset! selection-made? false)
              ; If no selection was made, treat it as a
              ; simple click for now...
              (do
                (reset! waiting? true)
                ; Wait an appropriate amount of time for the
                ; double click to happen...
                (js/setTimeout
                  (fn []
                    ; If we are still waiting for the double click
                    ; to happen, it didn't happen! The mouse-down
                    ; handler would have set waiting? to false
                    ; by now if it had been clicked a second time.
                    ; (Remember that the mouse down handler runs
                    ; before the click handler since the click handler
                    ; runs only once the mouse is released.
                    (when @waiting?
                      (single-click-fn e)
                      (reset! waiting? false)))
                  500))))
           :on-mouse-down
           (fn [e]
            ; Set this for the click handler in case a double
            ; click is happening.
            (reset! waiting? false)
            ; Only run this if it is a left click, or the event
            ; listener is not removed until a single click on this
            ; segment is performed again, and will listen on
            ; every click everywhere in the window.
            (when (-> e .-button zero?)
              (js/console.log "mouse down handler running")
              (.addEventListener js/document "selectionchange" selection-handler)))
           :on-double-click #(double-click-fn %)}
          some content here]])))