diff --git a/src/content/demo/stacking/code.js b/src/content/demo/stacking/code.js new file mode 100644 index 00000000..9a435597 --- /dev/null +++ b/src/content/demo/stacking/code.js @@ -0,0 +1,117 @@ +const debounce = (fn, timeout) => { + let timeoutId = null; + return (...args) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + fn(...args); + }, timeout); + return timeoutId; + }; +}; + +const canvas = new fabric.Canvas(canvasEl); + +const emojiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣"]; + +let intersecting = false; + +let index = 2; +const indexSelectEl = document.getElementById("index"); +indexSelectEl.addEventListener("change", (e) => { + index = parseInt(e.target.value, 10) - 1; +}); + +const objects = await Promise.all( + emojiNumbers.map(async (emoji, i) => { + const emojiDecoration = `${emoji}`; + const { objects, options } = await fabric.loadSVGFromString( + emojiDecoration + ); + + const shape = fabric.util.groupSVGElements(objects, options); + shape.set({ + left: 50 + i * 75, + top: 50 + i * 50, + width: 128, + height: 128, + shadow: new fabric.Shadow({ + color: "rgba(0,0,0,0.5)", + offsetY: 5, + blur: 10, + }), + }); + + canvas.add(shape); + + return shape; + }) +); + +const bringObjectForward_Btn = document.getElementById("bringObjectForward"); +bringObjectForward_Btn.onclick = function () { + canvas.bringObjectForward(objects[index], intersecting); +}; +const sendObjectBackwards_Btn = document.getElementById("sendObjectBackwards"); +sendObjectBackwards_Btn.onclick = function () { + canvas.sendObjectBackwards(objects[index], intersecting); +}; +const intersecting_Checkbox = document.getElementById("intersecting"); +intersecting_Checkbox.addEventListener("change", (e) => { + intersecting = e.target.checked; +}); + +const bringObjectToFront_Btn = document.getElementById("bringObjectToFront"); +bringObjectToFront_Btn.onclick = function () { + canvas.bringObjectToFront(objects[index]); +}; + +// todo: negative numbers actually "work", as they are passed to `splice`, but that +// behavior seems confusing and unwanted -- or at least should be documented? +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice#start +const moveObjectTo_Btn = document.getElementById("moveObjectTo"); +moveObjectTo_Btn.onclick = function () { + const currentIndex = canvas._objects.findIndex((o) => o === objects[index]); + const userInput = prompt("moveObjectTo", currentIndex); + const stackIndex = parseInt(userInput, 10); + const invalid = !(stackIndex > -1); + if (userInput !== stackIndex.toString()) { + console.log( + `Changed "${userInput}" to "${stackIndex}"` + + (invalid ? ", invalid input" : "") + ); + } else if (invalid) { + console.log("ignoring", userInput); + } + if (invalid) return; + canvas.moveObjectTo(objects[index], stackIndex); +}; + +const sendObjectToBack_Btn = document.getElementById("sendObjectToBack"); +sendObjectToBack_Btn.onclick = function () { + canvas.sendObjectToBack(objects[index]); +}; + +const preserveObjectStacking_Checkbox = document.getElementById( + "preserveObjectStacking" +); +preserveObjectStacking_Checkbox.addEventListener("change", (e) => { + canvas.preserveObjectStacking = e.target.checked; +}); + +const stackInfo_P = document.getElementById("stackInfo"); +const debouncedAfterRender = debounce((e) => { + stackInfo_P.textContent = canvas._objects.map((obj) => obj.text).join(", "); + + const activeObjects = canvas + .getActiveObjects() + .map((obj) => emojiNumbers.indexOf(obj.text)); + console.log("activeObjects", activeObjects); + if (activeObjects.length !== 1) return; + const activeObject = activeObjects[0]; + if (index === activeObject) return; + index = activeObject; + indexSelectEl.value = `${index + 1}`; +}, 100); +canvas.on("after:render", debouncedAfterRender); diff --git a/src/content/demo/stacking/index.mdx b/src/content/demo/stacking/index.mdx new file mode 100644 index 00000000..4cd14a81 --- /dev/null +++ b/src/content/demo/stacking/index.mdx @@ -0,0 +1,64 @@ +--- +date: "2012-07-25" +title: "Stacking" +description: "Stacking in FabricJS" +thumbnail: "stacking.png" +tags: ["stack", "stacking", "z-index"] +--- + +import { CodeEditor } from "../../../components/CodeEditor"; +import code from "./code?raw"; + +This demo showcases several methods for changing the object stack order, meaning the order in which they are drawn on the canvas. + +Ultimately, the `canvas._objects` array determines the stack order. The following functions all re-arrange that array. + +[`bringObjectForward`](/api/classes/canvas/#bringobjectforward) & [`sendObjectBackwards`](/api/classes/canvas/#sendobjectbackwards) both accept an optional `boolean` 2nd argument, `intersecting`, which limits changes to intersecting (overlapping) objects. + +By default, when an object is selected, it is temporary drawn above all non-selected objects. Setting [`preserveObjectStacking`](/api/classes/canvas/#preserveobjectstacking) to `true` prevents that. + + + +
+ + + + + + +
+ + + + + +
+ + + +

+ Stack order: +

+ + + + diff --git a/src/content/demo/stacking/stacking.png b/src/content/demo/stacking/stacking.png new file mode 100644 index 00000000..fcfc9b35 Binary files /dev/null and b/src/content/demo/stacking/stacking.png differ