Card Animation Prototype
Click here to open prototype in new window
A fundamental part of Solitaire Pack is animating cards from one pile to another. In general, this process consists of the following steps:
Figuring out exactly how to perform the animation was somewhat complicated. I began by sitting down and drawing sketches and examining which values needed to be calculated, and how these values would be used in each step of animation. Note: Unfortunately, the algorithm described here had to be totally revamped for the actual program, because it performed painfully slow in some animation situations.
The prototype contains the following elements:
The prototype allows you to drag the destination pile around to set its position. Also, the cards can be positioned by dragging them within the fragment pile. To set the cards' destination offsets, enter numbers in the edit fields labeled "card destination coordinates." Once you are ready, click the Calculate button. Important values calculated will be displayed on the left column of the page. To see a single step of the animation, click the Step button; to run the animation in its entirety, click Animate. After the animation is finished, drag the elements around and try again. The animation works by first calculating the absolute positions of each card, both their current positions in the fragment and their destination position. Then, the distance that each must travel is computed. The card that has to travel the farthest is used to calculate the rest of the values. The code below is taken from the animation prototype script.
pf = pileFrag.style; pd = pileDest.style; var nSteps; // number of steps var curStep = 0; // which step is current var p_stepX; // amount the pile moves in each step var p_stepY; // amount the pile moves in each step var c_stepX = new Array(2); // amounts each card moves in each step var c_stepY = new Array(2); // amounts each card moves in each step var p1_relX; // f.x in diagram var p1_relY; // f.y var p2_relX; // e.x var p2_relY; // e.y var c1_X = new Array(2); // s.x var c1_Y = new Array(2); // s.y var c2_X = new Array(2); // p.x var c2_Y = new Array(2); // p.y var p_dX; // pile delta x var p_dY; // pile delta y var c_absdX = new Array(2); // (e.x + p.x) - (f.x + s.x) var c_absdY = new Array(2); // (e.y + p.y) - (f.y + s.y) var dists = new Array(2); // hypontenuse distances of each card // calculates the values function calc() { p1_relX = pileFrag.style.pixelLeft; p1_relY = pileFrag.style.pixelTop; p2_relX = pileDest.style.pixelLeft; p2_relY = pileDest.style.pixelTop; c1_X[0] = theCard.style.pixelLeft; c1_Y[0] = theCard.style.pixelTop; c2_X[0] = new Number(doX.value); c2_Y[0] = new Number(doY.value); c1_X[1] = theCard2.style.pixelLeft; c1_Y[1] = theCard2.style.pixelTop; c2_X[1] = new Number(doX2.value); c2_Y[1] = new Number(doY2.value); p_dX = p2_relX - p1_relX; p_dY = p2_relY - p1_relY; c_absdX[0] = (p2_relX + c2_X[0]) - (p1_relX + c1_X[0]); c_absdY[0] = (p2_relY + c2_Y[0]) - (p1_relY + c1_Y[0]); c_absdX[1] = (p2_relX + c2_X[1]) - (p1_relX + c1_X[1]); c_absdY[1] = (p2_relY + c2_Y[1]) - (p1_relY + c1_Y[1]); dists[0] = Math.sqrt(Math.pow(c_absdX[0],2) + Math.pow(c_absdY[0],2)); dists[1] = Math.sqrt(Math.pow(c_absdX[1],2) + Math.pow(c_absdY[1],2)); nSteps = Math.ceil(Math.max(dists[0], dists[1]) / 10); p_stepX = p_dX/nSteps; p_stepY = p_dY/nSteps; c_stepX[0] = (c2_X[0]-c1_X[0])/nSteps; c_stepY[0] = (c2_Y[0]-c1_Y[0])/nSteps; c_stepX[1] = (c2_X[1]-c1_X[1])/nSteps; c_stepY[1] = (c2_Y[1]-c1_Y[1])/nSteps; curStep = 0; // non-calculation stuff omitted } // performs a step of the animation function dostep() { if(curStep >= nSteps) { animDone(); return; } curStep++; if(curStep == nSteps) { // last animation step: move all to final positions pileFrag.style.pixelLeft = pileDest.style.pixelLeft; pileFrag.style.pixelTop = pileDest.style.pixelTop; theCard.style.pixelLeft = c2_X[0]; theCard.style.pixelTop = c2_Y[0]; theCard2.style.pixelLeft = c2_X[1]; theCard2.style.pixelTop = c2_Y[1]; animDone(); } else { // update positions p1_relX += p_stepX; p1_relY += p_stepY; c1_X[0] += c_stepX[0]; c1_Y[0] += c_stepY[0]; c1_X[1] += c_stepX[1]; c1_Y[1] += c_stepY[1]; pileFrag.style.pixelLeft = p1_relX; pileFrag.style.pixelTop = p1_relY; theCard.style.pixelLeft = c1_X[0]; theCard.style.pixelTop = c1_Y[0]; theCard2.style.pixelLeft = c1_X[1]; theCard2.style.pixelTop = c1_Y[1]; } } function stepanim() { dostep(); anim(); } function anim() { if(curStep < nSteps) { btnCalc.disabled=true; btnStep.disabled=true; btnAnim.disabled=true; window.setTimeout(stepanim, 0); } } |