我希望看到“go”宏的一种变体,该变体立即执行直到第一个可停用的操作,而不是分发(实质上调用“setTimeout”两次)。这主要适用于 CLJS,因为 CLJ 可以通过阻塞操作逃逸,但这也可能在这里有所帮助。
我的用例
我目前使用 core.async 协调 CSS 动画,虽然它使用 "go" 工作得很好,但它确实需要关注操作的顺序,因为 "go" 会将控制权交回浏览器,浏览器可能会决定重新流/重绘屏幕,尽管动画尚未正确开始。
示例
`
(defn show-toast [message]
(let [toast (create-toast-dom message)]
(dom/append js/document.body toast)
;; X - PROBLEM: REPAINT HAPPENS NOW
(go (<! (anim/slide-in-from-bottom animation-time toast))
(<! (timeout 3000))
(<! (anim/fade-out animation-time toast))
(dom/remove toast)
)))
`
此示例尝试创建一个 "Toast" DOM 节点(链接:1),它从底部开始动画,并在 3 秒后移除。一个真正的实现将需要处理点击等操作,但该示例足够简单地描述问题。{{(anim/...)}} 函数设置了适当的 CSS,并返回一个 {{(async/timeout time)}} 通道,使我们能够等待 "完成"。
问题在于 X,go 被分发,并将控制权交回浏览器。因此,动画的 "设置" 部分被延迟,浏览器以原始状态渲染 toast DOM 节点。通常这意味着浏览器将进行重绘。这会有一个 "闪烁" 效果,节点出现然后消失并动画化(取决于默认 CSS)。
简单的解决方案是将 {{dom/append}} 调用移动到 go 块,这在当前示例中很容易,但对于更复杂的情况就困难多了。另一个解决方案是在 go 外部开始动画
`
(defn show-toast [message]
(let [toast (create-toast-dom message)]
(dom/append js/document.body toast)
(let [slide-in (anim/slide-in-from-bottom animation-time toast)]
;; REPAINT, but slide-in animation already started so no "flicker"
(go (<! slide-in)
(<! (timeout 3000))
(<! (anim/fade-out animation-time toast))
(dom/remove toast)
))))
`
这里也简单,但对于更复杂的事情就困难多了。
我希望看到一个被称为 "go!" 的变体,它不是分发 go 块,而是直接开始执行它。
`
(defn show-toast [message]
(let [toast (create-toast-dom message)]
(dom/append js/document.body toast)
(go! (<! (anim/slide-in-from-bottom animation-time toast)) ;; REPAINT ON <!
(<! (timeout 3000))
(<! (anim/fade-out animation-time toast))
(dom/remove toast)
)))
`
微小的区别是,在准备好之前,浏览器没有机会做任何事情,并且我们仍然会等待。在我自己调整了一些动画效果后,总是需要将内容移出或移入当前的 go 块。
我知道这是一个相当特定的用例,core.async 可能并非是完成此事的最佳方式,但到目前为止我已经取得了很大的成功,代码也很容易推理。
我没有深入到 core.async 的内部,但 go!
应该是一个相当简单的补充。如果这个改变被接受,我会开始研究并创建一个补丁,只是想确保这样做之前没有反对意见。
希望我的意图清晰。CSS动画有时候可能不太直观,这可能导致问题实际所在的位置并不明显。
(链接:1) http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs