我希望看到一个 "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 秒后移除。实际实现需要处理点击等事件,但示例足够简单,足以描述问题。The {{(anim/...)}} 函数设置了适当的 CSS 并返回一个 {{(async/timeout time)}} 通道,它让我们等待 "完成"。
在 X 处的问题是 gor 已经分发并且控制权被交还给浏览器。因此,动画的 "设置" 部分被延迟,并且 toast DOM 节点被浏览器直接渲染。通常这意味着浏览器会进行重绘。这会产生“闪烁”效果,因为节点出现然后消失并动画化(取决于默认的 CSS)。
简单的方法是将 {{dom/append}} 调用移动到 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