Create Dynamic Sections with Drag-and-Drop Functionality and Customizable Textboxes
Explore how to use Fabric.js 5 to create dynamic sections with draggable textboxes, enhancing your web application's interactivity.
Fabric.js 5 is a powerful library that makes it easy to create and manipulate graphics and other visual elements on the web. One of the features it offers is the section component, which allows users to add, update, and drag textboxes within a rectangle. This feature is extremely useful for web developers who want to create visually appealing designs and layouts for their applications.
With Fabric.js 5, you can easily create a customizable section component that enhances the user experience and adds an extra layer of interactivity to your web pages. In this post, we'll explore how to create a section component with Fabric.js 5 and delve into the many ways in which it can be used to improve your website's design and functionality.
Fabric.js 5 and the Section Component
Fabric.js 5 is a powerful JavaScript library that provides developers with a wide range of features for building interactive web applications. With Fabric.js 5, developers can create and manipulate graphics, images, and other visual elements on the web page with ease.
One of the key features of Fabric.js 5 is its ability to create custom components that can be used to build complex user interfaces. The Section component is one such custom component that can be created with Fabric.js 5. The Section component is essentially a rectangular container that can hold one or more text boxes, which can be added, updated, and dragged around by the user.
The Section component offers several benefits to web developers. First and foremost, it provides a simple and intuitive way to organize text content on a web page. By using the Section component, developers can easily create visually appealing layouts that are easy to read and navigate.
Additionally, the Section component is highly customizable, allowing developers to easily adjust the size, shape, and appearance of the container to suit their specific needs. This level of flexibility makes it easy to integrate the Section component into a wide range of web applications.
Overall, the Section component is a powerful tool for web developers looking to create dynamic and engaging user interfaces. With its intuitive design and wide range of customization options, the Section component is sure to be a valuable asset for any web development project.
Creating a Rect, Input Field, and Add Button
let rectHeight = 200;
var rect = new fabric.Rect({
left: 50,
top: 100,
fill: "#F8F8F8",
width: 300,
height: rectHeight,
stroke: "black",
strokeWidth: 1,
originX: "left",
originY: "top",
id: Date.now()
});
var textbox = new fabric.TextboxWithPadding("Input field", {
left: rect.left + 15,
top: rect.top + 30,
width: rect.width - 35,
height: 30,
fontSize: 16,
fill: "black",
backgroundColor: "#ffffff",
borderColor: "#9181fc",
//@ts-ignore
originalWidth: rect.width - 35,
originalTop: rect.top + 30,
padding: 5,
id: Date.now()
});
canvas.add(rect);
canvas.add(textbox);
textbox.setControlsVisibility({
mtr: false,
tr: false,
tl: false,
bl: false,
br: false
});
textbox.set({
//@ts-ignore
originalWidth: rect.width,
originalHeight: 30,
originalTop: 110
});
var addButton = new fabric.Rect({
left: rect.left,
top: rect.top + rect.getScaledHeight() + 30,
fill: "#E7E7E7",
width: 60,
height: 30,
stroke: "black",
strokeWidth: 0,
corneradius: 5,
className: "addButton"
});
var addText = new fabric.Text("+ Add", {
left: addButton.left + addButton.getScaledWidth() / 2,
top: addButton.top + addButton.getScaledHeight() / 2,
fontSize: 14,
fill: "#000000",
originX: "center",
originY: "center",
//@ts-ignore
id: Date.now()
});
var group = new fabric.Group(addButton, addText, {
left: rect.left,
top: rect.top + rect.getScaledHeight() + 30,
originX: "left",
originY: "center",
subTargetCheck: true,
lockMovementX: true,
lockMovementY: true,
lockScalingX: true,
lockScalingY: true,
hasControls: false,
hasBorders: false,
hoverCursor: "pointer",
//@ts-ignore
id: Date.now()
});
canvas.add(group);
var lastTextboxY = textbox.top + textbox.getScaledHeight() + 30;
The provided code creates a rectangular fabric object with a textbox, an add button, and a group of fabric objects on a canvas. The properties of each object are defined in the code, including their positioning, size, and colors.
This will make:
The code starts with an event listener that is triggered when the user clicks the mouse on the canvas. The listener checks if the mouse click targeted a specific group and, if so, repositions the textboxes within that group. Next, the code checks if the group contains an object with a specific class name (in this case, an 'addButton'). If it does, the code creates a new textbox with some default settings, such as its position and size, and adds it to the canvas.
After the new textbox is created, the code checks if it overlaps with any existing textboxes in the group. If there is no overlap, the code animates the textbox by setting its opacity to 0 and gradually increasing it to 1 throughout 200 milliseconds. The animation uses an easing function that starts slow, speeds up, and then slows down again. After the animation is complete, the code sets the textbox's coordinates and adjusts the size of the group to accommodate the new textbox.
canvas.on("mouse:down", function (options) {
if (options.target && options.target === group) {
repositionSectionTextboxes();
// Check if the group contains a child with an ID of 'addButton'
var addButton = group.getObjects().find(function (obj) {
return obj.className === "addButton";
});
if (addButton && options.target === group) {
var newTextbox = new fabric.TextboxWithPadding("Input field", {
left: rect.left + 15,
width: rect.getScaledWidth() - 35,
top: lastTextboxY,
height: textbox.getScaledHeight() | | 30,
fontSize: 16,
// hasControls: false,
originalTop: lastTextboxY,
padding: 5,
fill: "black",
backgroundColor: "#ffffff",
borderColor: "#9181fc",
id: Date.now()
});
// Check if the new textbox will overlap with any existing textbox
var overlapping = group.getObjects().some(function (obj) {
return obj !== addButton && obj.intersectsWithObject(newTextbox);
});
if (!overlapping) {
canvas.add(newTextbox);
newTextbox
.set({
//@ts-ignore
originalWidth: rect.width,
originalHeight: 30,
originalTop: lastTextboxY,
opacity: 0 // Set opacity to 0 for fade in effect
})
.setCoords();
// Animate the object to fade in
newTextbox.animate("opacity", 1, {
duration: 200, // Set the duration of animation (in milliseconds)
onChange: canvas.renderAll.bind(canvas), // Re-render the canvas on each frame of animation
onComplete: function () {
newTextbox.setCoords(); // Set the final position of the object after animation
},
easing: fabric.util.ease.easeInOutCubic
});
newTextbox.setControlsVisibility({
mtr: false,
tr: false,
tl: false,
bl: false,
br: false
});
// Calculate the required height for the rect based on the total height of all the textboxes
var totalHeight =
lastTextboxY + newTextbox.getScaledHeight() - rect.top + 30;
rectHeight = rect.getScaledHeight();
if (totalHeight > rectHeight) {
rect
.set({
height: totalHeight / rect.scaleY
})
.setCoords();
canvas.renderAll();
}
// Set the top position of the group based on the total height of the group
group.set({
top: rect.top + rect.getScaledHeight() + 30
});
group.setCoords();
group.setCoords();
canvas.renderAll();
lastTextboxY = newTextbox.top + newTextbox.getScaledHeight() + 30;
}
}
}
if (options.target && options.target.type === "textbox") {
let showControls = false;
if (!rect.intersectsWithObject(options.target, true, true)) {
showControls = true;
options.target.hasBorders = true;
}
options.target.setControlsVisibility({
mtr: showControls,
tr: showControls,
tl: showControls,
bl: showControls,
br: showControls
});
options.target.setCoords();
canvas.requestRenderAll();
}
});
canvas.on("object:scaling", function (options) {
var scaledObject = options.target;
scaledObject.setCoords();
const isText = scaledObject.type === "textbox";
if (isText | | scaledObject.type === "image") {
canvas.uniformScaling = true;
} else {
//@ts-ignore
if (
"bl", "br", "tr", "tl".indexOf(scaledObject.transform.corner) !== -1
) {
canvas.uniformScaling = false;
}
}
if (scaledObject === rect) {
let width = rect.getScaledWidth() - 25;
let totalHeight = 0;
let count = 0;
canvas.forEachObject(function (obj) {
if (obj.type === "textbox") {
totalHeight += obj.getScaledHeight() + 30;
++count;
}
});
let averageHeight = totalHeight / count;
let index = 0;
canvas.forEachObject(function (obj) {
if (obj.type === "textbox") {
obj
.set({
width: width,
top: rect.top + index * averageHeight + 30
})
.setCoords();
obj.scaleToWidth(width);
++index;
}
});
// update the lastTextboxY variable based on the new position of the last textbox
let lastTextbox = canvas
.getObjects()
.filter((obj) => obj.type === "textbox")
.pop();
lastTextboxY = lastTextbox.top + lastTextbox.getScaledHeight() + 30;
}
repositionSectionTextboxes();
let newTop = rect.top + rect.getScaledHeight() + 30;
if (lastTextboxY > newTop) {
newTop = lastTextboxY + 30;
}
// Set the top position of the group based on the total height of the group
group
.set({
top: newTop
})
.setCoords();
canvas.renderAll();
});
canvas.on("object:moving", function (options) {
var movedObject = options.target;
movedObject.setCoords();
var shadow = new fabric.Shadow({
color: "rgba(0, 0, 0, 0.2)",
offsetX: 0,
offsetY: 0.3,
blur: 3
});
// Add shadow to the moving object
movedObject.set({
shadow
});
if (movedObject === rect) {
const lastLeft = movedObject.get("lastLeft") | | movedObject.left;
const lastTop = movedObject.get("lastTop") | | movedObject.top;
var deltaX = movedObject.left - lastLeft;
var deltaY = movedObject.top - lastTop;
canvas.forEachObject(function (obj) {
if (
obj.type === "textbox" &&
obj.intersectsWithObject(movedObject, true, true)
) {
obj
.set({
left: obj.left + deltaX,
top: obj.top + deltaY
})
.setCoords();
}
});
var addButton = group.getObjects().find(function (obj) {
return obj.className === "addButton";
});
if (addButton) {
group
.set({
left: movedObject.left,
top: movedObject.top + movedObject.getScaledHeight() + 30
})
.setCoords();
}
movedObject.set({
lastLeft: movedObject.left,
lastTop: movedObject.top
});
var lastTextbox = canvas
.getObjects()
.filter(function (obj) {
return obj.type === "textbox";
})
.pop();
if (lastTextbox) {
lastTextboxY = lastTextbox.top + lastTextbox.getScaledHeight() + 30;
}
}
// Check if the moving object is a textbox
if (movedObject.type === "textbox") {
var movingBounds = movedObject.getBoundingRect();
// Loop through all objects on the canvas
canvas.forEachObject(function (obj) {
if (obj === movedObject) {
return;
}
// Check if the object is a textbox and if it's overlapping with the moving object
if (obj.type === "textbox" && movedObject.intersectsWithObject(obj)) {
// Check if the overlapping object was moved down previously
var objBounds = obj.getBoundingRect();
let top = movingBounds.top + objBounds.height + 2;
if (objBounds.top > movingBounds.top) {
// Move the overlapping object back up to its original position
top = movingBounds.top - objBounds.height;
}
if (top < rect.top) {
top = rect.top;
}
obj.top = top;
// Set the new position of the object and re-render the canvas
obj.setCoords();
canvas.renderAll();
}
});
}
});
canvas.on("mouse:up", function () {
repositionSectionTextboxes();
});
canvas.on("text:changed", function (options) {
var textbox = options.target;
textbox.bringToFront();
canvas.renderAll();
});
The code also includes event listeners for scaling and moving objects on the canvas. When an object is scaled, the code sets a flag to indicate whether it is a textbox or an image. If it is a textbox, the code sets the canvas's uniformScaling property to true. If it is an image or any other object, the code sets uniformScaling to false. When an object is moved, the code updates its coordinates and repositions the textboxes in the group.
Copy & paste in the textbox inside the section
let clipboard = [];
let copyIndex = -1;
let copyObject = {};
// Add event listener for copy and paste
document.addEventListener("keydown", function (e) {
if (e.keyCode === 67 && (e.ctrlKey | | e.metaKey)) {
// Ctrl + C or Command + C pressed
const activeObject = canvas.getActiveObject();
if (!activeObject) {
return true;
}
copyIndex = canvas
.getObjects()
.findIndex((object) => object === activeObject); // Get the index of obj in getObjects()
copyObject = activeObject;
clipboard =
activeObject && activeObject.getObjects
? activeObject.getObjects()
: activeObject;
} else if (e.keyCode === 86 && (e.ctrlKey | | e.metaKey)) {
clipboard.forEach((object) => {
if (object.type !== "textbox") {
return true;
}
var newTextbox = new fabric.TextboxWithPadding(object.text, {
left: rect.left + 15,
width: rect.getScaledWidth() - 35,
top: copyObject.top + 30,
height: textbox.getScaledHeight() | | 30,
fontSize: 16,
// hasControls: false,
originalTop: lastTextboxY,
padding: 5,
fill: "black",
backgroundColor: "#ffffff",
borderColor: "#9181fc"
// id: Date.now()
});
// Check if the new textbox will overlap with any existing textbox
var overlapping = group.getObjects().some(function (obj) {
return obj !== addButton && obj.intersectsWithObject(newTextbox);
});
if (!overlapping) {
canvas.add(newTextbox);
// insert new object at index position 2
canvas.getObjects().splice(copyIndex, 0, newTextbox);
newTextbox
.set({
//@ts-ignore
originalWidth: rect.width,
originalHeight: 30,
originalTop: lastTextboxY,
opacity: 0 // Set opacity to 0 for fade in effect
})
.setCoords();
// Animate the object to fade in
newTextbox.animate("opacity", 1, {
duration: 200, // Set the duration of animation (in milliseconds)
onChange: canvas.renderAll.bind(canvas), // Re-render the canvas on each frame of animation
onComplete: function () {
newTextbox.setCoords(); // Set the final position of the object after animation
},
easing: fabric.util.ease.easeInOutCubic
});
newTextbox.setControlsVisibility({
mtr: false,
tr: false,
tl: false,
bl: false,
br: false
});
var topmostTextbox = canvas
.getObjects()
.filter(function (obj) {
return obj.type === "textbox";
})
.sort(function (a, b) {
return b.top - a.top;
})0;
let top = topmostTextbox.top;
if (!topmostTextbox.id | | topmostTextbox.id === copyObject.id) {
top -= 30;
}
newTextbox.set({
id: Date.now()
});
rect
.set({
height: top
})
.setCoords();
canvas.renderAll();
// Set the top position of the group based on the total height of the group
group.set({
top: rect.top + rect.getScaledHeight() + 30
});
group.setCoords();
canvas.renderAll();
canvas.setActiveObject(newTextbox);
}
repositionSectionTextboxes();
});
clipboard = [];
copyIndex = -1;
copyObject = {};
// update the lastTextboxY variable based on the new position of the last textbox
let lastTextbox = canvas
.getObjects()
.filter((obj) => obj.type === "textbox")
.pop();
lastTextboxY = lastTextbox.top + lastTextbox.getScaledHeight();
canvas.requestRenderAll();
}
});
Working demo
Why Static Site Generation Beats SPAs for SEO | What is a static site generator?
A static website generator builds fast, SEO-friendly HTML pages automatically. Discover the pros and cons of using one for modern sites and apps.
HOW TO Effortlessly Publish Your JavaScript Project as an NPM Module
Effortlessly Publish Your JavaScript Project as an NPM Module with our step-by-step guide. Discover how to share your work and boost visibility today!
A Comprehensive Guide to Working with Objects and Shapes in Fabric.js
This guide explores the extensive capabilities of Fabric.js in creating and manipulating objects and shapes on the web. Learn how to bring your web graphics to life.
A Comprehensive Step-by-Step Guide: Customizing Rotate Icons in Fabric.js for Programmers
This guide provides a comprehensive overview of how to customize the rotate icon in Fabric.js, enhancing your canvas projects with unique designs.
Achieving Pixel-Perfect Alignment: Exploring Object Snapping with SnappyRect in Fabric.js
This article explores the SnappyRect class in Fabric.js, a custom solution for achieving pixel-perfect object alignment through object snapping.
Angular vs React: Which Framework Wins for Scalable Enterprise Apps?
This blog post compares Angular and React, analyzing their strengths and weaknesses for building scalable enterprise applications, helping you choose the right framework for your project.
Best NodeJS Architecture for Scalable Cloud Apps: Monolith vs Microservices vs Serverless (Shorterloop’s Guide)
Discover Shorterloop's insights on the best NodeJS architectures for building scalable cloud applications. We compare monolithic, microservices, and serverless approaches, providing real examples to help you decide.
Boosting Performance in Fabric.js 5: Essential Optimization Techniques and Tips
Discover key optimization techniques to enhance the performance of Fabric.js. This guide provides practical tips for creating responsive applications.
Create Dynamic Sections with Drag-and-Drop Functionality and Customizable Textboxes
Explore how to use Fabric.js 5 to create dynamic sections with draggable textboxes, enhancing your web application's interactivity.
Create Professional-Looking Textboxes in Fabric.js with Strokes and Padding
This blog post covers how to create professional-looking stroked textboxes in Fabric.js, including customization options for padding and rounded corners.
Creating Interactive Diagrams with Fabric.js: A Guide to Ports and Connector Lines
In this guide, we explore creating interactive diagrams using Fabric.js by adding ports and connector lines, enhancing user interaction and visual clarity.
Express.js vs Hono.js: The Ultimate Framework Showdown for Node.js Developers
This blog post compares Express.js and Hono.js, examining their features, performance, and suitability for modern web development. Make an informed choice for your next project.
Fabric.js 5 Mastery: Effortlessly Deleting Selected Objects Programmatically
This blog post explores how to programmatically delete selected objects in Fabric.js 5, focusing on removing polygons and circles. Discover the remove method and practical implementations.
Finding the Middle Coordinates of the Vertices of a Polygon in Fabric.js
This blog post explains how to find the middle coordinates of a polygon's vertices in Fabric.js, simplifying object manipulation for developers. It offers a step-by-step guide and code examples for better understanding.
Optimizing Angular Performance: Change Detection Strategy OnPush vs Default
This blog post explores Angular's change detection strategies, comparing OnPush and Default to help developers optimize performance effectively.
Sequelize Associations: How a Misplaced belongsTo Broke Our Query (And How to Fix It)
This blog post explores a common mistake in Sequelize associations involving belongsTo and belongsToMany, detailing how this led to unexpected SQL query errors and their solutions.
Simplify Object Manipulation: Grouping and Ungrouping with Fabric.js
Learn how to simplify object manipulation in Fabric.js by grouping and ungrouping objects. This guide provides syntax and examples for effective canvas management.
Simplifying Code Review: Tips for Improving Readability
This article provides effective strategies for simplifying the code review process while improving code readability and quality in software development.
Step-by-Step Guide to Deploy NodeJS app with PM2 on AWS EC2 instance
This comprehensive guide walks you through the process of deploying a Node.js application on an AWS EC2 instance using PM2. Explore the crucial steps and benefits.
Step-by-Step Guide to Getting Started with Fabric.js in Angular 13
This post is a comprehensive guide to getting started with Fabric.js in your Angular 13 project, covering installation, examples, and editing techniques.
Stop Wasting API Calls: How to Cancel Pending Requests Like a Pro
Discover how to manage and cancel pending API requests effectively using AbortController to enhance performance and user experience.
Streamlining Your Node.js Projects: How GitHub Actions Can Revolutionize Your Workflow
This blog post explores the benefits of migrating from AWS Pipeline to GitHub Actions for Node.js projects, highlighting improved workflow management and automation capabilities.
The Power of Feature Flags: A Guide to Unlocking the Potential of Your Software
This guide explores the power of feature flags in software development, highlighting their benefits and practical implementation strategies to enhance user experience.
Unleashing the Power of Fabric.js Canvas Events: Elevating Interactivity and User Experience
This article explores the functionalities of Fabric.js canvas events, showcasing methods to amplify web interactivity and improve user experiences.
What is Test Driven Development (TDD)? Definition, Benefits, Example, and more!
Learn about Test Driven Development (TDD), a pivotal software development method that enhances code quality through the Red/Green/Refactor cycle, alongside its benefits and limitations.