我希望看到 "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"(链接:1)DOM 节点,从底部动画进入并在 3 秒后移除。真正的实现需要处理点击等操作,但示例足够简单,可以描述问题。{{(anim/...)}} 函数设置合适的 CSS 并返回一个 {{(async/timeout time)}} 通道,使我们能够等待 "完成"。
在 X 点的问题在于 go 已被调度并交还了控制权。因此,动画的 "设置" 部分被推迟,吐司 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