The article is inspired by Tim’s CSS Classes State Machine Puzzle.
We had some frustrations all the times, on animating a window correctly.
Recently I try to dedicate on solving the puzzle. But at first I need to apologize for my poor knowledge and thought about css transitions using class names.
In the past I usually thought it was stupid to use class list to control the style. I thought it was ambiguous and strange if we ran into a situation that there’re more than one classes of same type put on one element. And an element full of different class names for different purposes may conflict. Recently I see Effeckt and know this is the trend: a simple class name stands for a kind of animation.
The real problem is that we should never put wrong class on the DOM element, in javascript. So this now turns to be a pure javascript puzzle.
We know, that, in certain moment, the app window is always in a specific state A, not B nor C. So what’s the problem? It’s how could we switch the state correctly.
Let’s create a real state machine in javascript!
So far it doesn’t take times for us to figure out that a window should have 4 basic transition state: closed
, opening
, opened
, closing
.
A basic transition life cycle of a window could be:
(initial) -> closed -> opening -> opened ——> closing -> opening ——> ….
Now we have 4 states, the next is what’s the trigger to states switching?
In finite state machine, what triggers state change is named for ‘event’.
We know that at least we have 2 events: ‘open’ and ‘close’.
- ‘open’ would trigger
closed
switches toopening
- ‘close’ would trigger
opened
switches toclosing
- If we sends ‘open’ event into the state machine continously, the second event would be ignored. (Exactly it depends on what policy we choose. For example, we could devide ‘opening’ state into ‘opening-part-1’ and ‘opening-part-2’ states. And implement the async state change. But if we need s two-state opening, it sounds like a CSS design problem to me. Let’s discuss this later if necessary.)
- In order to gurantee the transition does really end and thus being independent from the ‘animationend’ event(The event here is HTML DOM Event), we need to add a timer between ‘opening’ and ‘opened’ state. Also between ‘closing’ and ‘closed’ state.
- Let’s call the new event ‘timeout’, the timing we set the timer is right after the transitioning from ‘closed’ to ‘opening’, and from ‘opened’ to ‘closing’ successfully occurs.
- For some use case we may want to cancel the transition. The ‘cancel’ event is only valid in ‘opening’ and ‘closing’ state.
So far, the state machine for transition we have:
Now the problem is, how to represent this machine in javascript?
My anwser is put every transition relevant functions/attributes in another mixin object, which would be mixed into appWindow.prototype:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
|
The state machine’s usage and notes
- A single app window instance would send ‘open’ and ‘close’ event due to user action:
transitionStateMachine.processEvent(‘open’); transitionStateMachine.processEvent(‘close’);
Note, app window doesn’t need to know current state of the state machine. - The state machine itself is the one who creates/removes the timer which triggers timeout event. I am also thinking about moving this out of the state machine and do this in another mixin, only have the callback functions provided by the state machine. But I am not sure.
- The state machine has some callback for others(other state machine!) listed below:
- Enter a state successfully.
- Leave a state successfully.
- When an event triggers state switch successfully. In all these callback we would get the previous state and current state, and the event who triggers them.
- The CSS class for real UI closing animation is added in
enterClosing
and removed inenterClosed
. Or else we could do that inleaveOpened
andleaveClosing
. I have no strong opinion here. Maybe we could define the level of the callback into three here, according to the callback order. - The CSS class for real UI opening animation is added in
enterOpening
and removed inenterOpened
. - We could also move out (4) and (5) to another mixin to purify the state machine.
Finally, back to the problems addressed in Tim’s article:
- Do we need intermediate state?
- I don’t think so, at least for now. If we really need to goto next state when we successfully from state A to state B, we could call
_processEvent
again in the inner callback of the state machine. This doesn’t violate the policy that only state machine ifself could decide its next state. - If the intermediate state needs to acquire other type of state — just fetch the current state of the other state machine. Or, if we need, register an one-time callback if the current state doesn’t meet our requirement.
- I don’t think so, at least for now. If we really need to goto next state when we successfully from state A to state B, we could call
- How about state conflict between two apps?
- That won’t happen if we deal with the state changes correctly and independently in each app’s scope. I hope so.