【问题标题】:In ClojureScript, delay `:on-click` event and trigger only if `:on-double-click` event is not triggered在 ClojureScript 中,延迟 `:on-click` 事件并仅在 `:on-double-click` 事件未触发时触发
【发布时间】:2018-02-25 01:51:17
【问题描述】:

有什么简单的方法可以延迟: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"

编辑:

基于Taylor Wood's answer,这是一个抽象,它为你包装了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"]

【问题讨论】:

    标签: clojure clojurescript reagent


    【解决方案1】:

    这是一种方法:

    (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?,则意味着双击事件已经发生,将waiting?重置为false,并调用double-click-fn

    :on-click 处理程序使用compare-and-set 仅在我们尚未处于waiting? 状态时才采取行动,从而避免针对三次/四次点击等的一些不当行为。

    【讨论】:

      【解决方案2】:

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

      幸运的是,click 事件带有一个名为detail 的属性,该属性设置为连续点击的次数。我找到了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 %))
      

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


      剩下的内容是为那些有兴趣使文本选择作用于正在侦听单击的元素的人的附录。我从谷歌来到这里寻找如何做到这一点,所以也许它可以帮助某人。

      我遇到的一个挑战是我的应用程序规定用户可以执行双击一开始不释放第二次点击;有点像“一次半点击”。为什么?因为我正在监听跨度上的点击,用户可能会执行双击以选择整个单词,但随后按住第二次单击并拖动鼠标以选择原始单词旁边的其他单词。问题是双击事件处理程序仅在用户释放第二次单击后触发,因此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 处理程序仅在释放时触发(并且处理程序按鼠标按下、单击、双击的顺序触发),这给了 @ 987654335@ 处理程序将waiting? 设置为false 的机会,如果用户尚未释放鼠标,则需要这样做,因为他不会触发:on-double-click 事件处理程序。

      请注意,现在您甚至不需要在双击处理程序中将 waiting? 设置为 false,因为在双击处理程序运行时鼠标按下处理程序已经完成了这项工作。

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

      整个解决方案看起来像这样(但请记住,这是专门针对文本元素的,因此只是 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]])))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-30
        • 1970-01-01
        • 2023-03-23
        • 2012-02-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多