我希望看到一个 "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秒后删除。实际的实现需要处理点击等操作,但示例足够简单,可以描述问题。
问题在于X处,go被调度并将控制权交给了浏览器。因此动画的“设置”部分被延迟,浏览器将toast dom节点以原始方式渲染。
简单的解决方案是将{{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