card-factory
Version:
A comprehensive library for card manipulation
223 lines (222 loc) • 9.39 kB
JavaScript
export const slideCard = async (cardElement, vector, duration) => {
if (cardElement.transform.active)
return;
if (vector.length !== 2) {
throw "Error: vector must be an array of 2 values, x and y.";
}
const { scale, rotate } = cardElement.transform;
const newTranslate = `translate(${vector[0]}px, ${vector[1]}px)`;
cardElement.transform.translate = newTranslate;
const transform = `${newTranslate} ${scale} ${rotate}`;
const keys = {
transform: transform,
};
const options = {
duration: duration,
easing: "ease-out",
delay: 0,
direction: "normal",
};
const anim = cardElement.container.animate(keys, options);
cardElement.container.dispatchEvent(new Event("animationstart"));
return anim.finished.then((animation) => {
cardElement.container.style.transform = transform;
cardElement.container.dispatchEvent(new Event("animationend"));
return animation;
});
};
export const turnCard = async (cardElement, duration) => {
if (cardElement === undefined)
return new Promise(() => undefined);
if (cardElement.transform.active)
return new Promise(() => undefined);
cardElement.transform.rotate =
cardElement.transform.rotate === `rotate(0deg)`
? "rotate(90deg)"
: "rotate(0deg)";
const { translate, scale, rotate } = cardElement.transform;
const transform = `${translate} ${scale} ${rotate}`;
const keys = {
transform: transform,
};
const options = {
duration: duration,
easing: "linear",
delay: 0,
direction: "normal",
};
const anim = cardElement.container.animate(keys, options);
cardElement.container.dispatchEvent(new Event("animationstart"));
return anim.finished.then(() => {
cardElement.container.style.transform = transform;
cardElement.container.dispatchEvent(new Event("animationend"));
return Promise.resolve(true);
});
};
export const zoomCard = async (cardElement, factor, duration) => {
// eslint-disable-next-line prefer-const
let { translate, scale, rotate } = cardElement.transform;
scale = `scale(${factor})`;
const transform = `${translate} ${scale} ${rotate}`;
const keys = {
transform: transform,
};
const options = {
duration: duration,
easing: "ease-out",
delay: 0,
direction: "normal",
};
const anim = cardElement.container.animate(keys, options);
return anim.finished.then(() => {
cardElement.container.style.transform = transform;
});
};
//! I havent tested this
export const slideDeck = async (pile, vector, duration) => {
if (vector.length !== 2) {
throw "Error: vector must be an array of 2 values, x and y.";
}
const translate = `translate(${vector[0]}px, ${vector[1]}px)`;
const transform = `${translate} scale(1) rotate(0deg)`;
const keys = {
transform: transform,
};
const options = {
duration: duration,
easing: "ease-out",
delay: 0,
direction: "normal",
};
const anim = pile.container.animate(keys, options);
return anim.finished.then(() => {
pile.container.style.transform = transform;
});
};
/**
*
* This is an ASYNC function. You likely want to AWAIT this before performing more operations
* @param numberOfCards The number of cards to deal out
* @param from The pile the cards are coming from
* @param to The pile(s?) the cards are going to
* @param delayTime The delay between dealing cards
*/
export async function deal(numberOfCards, from, to, delayTime = 200) {
const piles = Array.isArray(to) ? to : [to];
const promises = [];
for (let i = 0; i < numberOfCards * piles.length; i++) {
const currentPile = piles[i % piles.length];
// Let animations run in parallel.
const animationPromise = from.moveCardToPile(currentPile);
if (!animationPromise) {
return Promise.all(promises);
} // Handle early exit
// Await the finished property inside the Promise array.
promises.push(animationPromise.then((animation) => animation?.finished?.then(() => true) ?? Promise.resolve(true)));
if (i < numberOfCards * piles.length - 1) {
await new Promise((resolve) => setTimeout(resolve, delayTime));
}
}
// Now we wait for all animations to complete
return Promise.all(promises);
}
export async function denyMove(cardElement) {
if (cardElement === undefined)
return new Promise(() => undefined);
if (cardElement.transform.active)
return new Promise(() => undefined);
const backgroundOverlay = document.createElement("div");
backgroundOverlay.classList.add("card-background-overlay");
// Append it inside the card container
cardElement.container.appendChild(backgroundOverlay);
// Get the computed z-index of the card container
const computedZIndex = window.getComputedStyle(cardElement.container).zIndex;
// If the z-index is not 'auto', set the overlay to be one level below
if (!isNaN(parseInt(computedZIndex)) && computedZIndex !== "auto") {
backgroundOverlay.style.zIndex = JSON.stringify(parseInt(computedZIndex));
}
else {
backgroundOverlay.style.zIndex = "1"; // Default if no valid z-index is found
}
backgroundOverlay.style.opacity = "0.2";
const keys = {
transform: [
"scale(1)",
"scale(1.1)",
"scale(1.1) translateX(-5px)",
"scale(1.1) translateX(5px)",
"scale(1.1) translateX(-5px)",
"scale(1)",
],
};
const options = {
duration: 600,
easing: "ease-in-out",
delay: 0,
direction: "normal",
composite: "add",
};
cardElement.container.style.backgroundColor = "red";
const anim = cardElement.container.animate(keys, options);
cardElement.container.dispatchEvent(new Event("animationstart"));
return anim.finished.then((promise) => {
cardElement.container.dispatchEvent(new Event("animationend"));
cardElement.container.removeChild(backgroundOverlay);
return promise;
});
}
// animates, and also moves the cardElement to new pile
export async function animateMoveCardToNewPile(source, destination, cardElement, index, groupOffset = 0) {
// ensure moving card has higher z-index than both decks
cardElement.container.style.zIndex = String(destination.cards.length + 1000);
// get the values for where the card is and where its going
const sourceBox = source.container.getBoundingClientRect();
const destinationBox = destination.container.getBoundingClientRect();
// The calculation for how far the card needs to be determined by the cascade vectors of the destination pile and the number of cards.
const destinationCascade = [
destination.cascadeOffset[0] *
cardElement.container.offsetWidth *
(destination.cards.length - 1),
destination.cascadeOffset[1] *
cardElement.container.offsetHeight *
(destination.cards.length - 1),
];
// When the card is appeneded to the new pile, and keeps the old transform, it will move that far away again.
const sourceCascade = [
source.cascadeOffset[0] *
cardElement.container.offsetWidth *
(index + groupOffset),
source.cascadeOffset[1] *
cardElement.container.offsetHeight *
(index + groupOffset),
];
// change the value of the cards transform to make it appear as it is still on top of the "source" pile
const { scale, rotate } = cardElement.transform;
const translate = `translate(${sourceBox.x - destinationBox.x + sourceCascade[0]}px, ${sourceBox.y - destinationBox.y + sourceCascade[1]}px)`;
cardElement.transform.translate = translate;
cardElement.container.style.transform = `${translate} ${scale} ${rotate}`;
// the vector to where slideCard will move the card to
const vector2 = [
destinationCascade[0],
destinationCascade[1],
];
cardElement.container.draggable = false;
const adjustZIndex = (cardElements) => {
for (let index = 0; index < cardElements.length; index++) {
const card = cardElements[index];
card.container.style.zIndex = String(index);
}
};
// animate the card moving from the current transform (appearing on source pile) to its rightful spot in destination cascade
return slideCard(cardElement, vector2, 600).then((animation) => {
// wait for the card to move, adjust the draggable setting to that of the new pile
cardElement.container.draggable = destination.options.draggable;
// adjust the ZIndex of both piles cardElements
adjustZIndex(destination.cardElements);
adjustZIndex(source.cardElements);
// We must adjust the transform on the card to be that of the destinations cascade now.
cardElement.transform.translate = `translate(${destinationCascade[0]}px, ${destinationCascade[1]}px)`;
cardElement.container.style.transform = `${`translate(${destinationCascade[0]}px, ${destinationCascade[1]}px)`} ${scale} ${rotate}`;
return animation;
});
}