Anonymous
Как вертикально выровнять компонент GrapesJS без преобразования его родительского элемента во flexbox?
Сообщение
Anonymous » 16 ноя 2025, 13:22
Проблема, с которой я столкнулся, связана с вертикальным выравниванием внутри компонента GrapesJS, но проблему можно воспроизвести даже без GrapesJS.
Вот небольшой работоспособный пример, показывающий точное поведение:
Код: Выделить всё
width: 300px;
height: 150px;
border: 1px solid #ccc;
background: #fafafa;
">
Content
Top
Middle
Bottom
document.querySelectorAll("button").forEach((btn) => {
btn.addEventListener("click", () => {
const parent = document.getElementById("parent");
// In GrapesJS this is parent.addStyle(...)
if (parent.style.display !== "flex") {
parent.style.display = "flex";
parent.style.flexDirection = "row";
}
parent.style.alignItems = btn.dataset.value;
});
});
styleManager.addType("horizontal-align-selector", {
create(_args?: any): HTMLElement {
const el = document.createElement("div");
el.className = "horizontal-align-selector-container";
el.style.cssText =
"display: flex; gap: 8px; padding: 10px 0; flex-wrap: wrap;";
const options: EnhancedRadioOption[] = [
{ value: "left", title: "Left", className: "fa fa-align-left" },
{
value: "center",
title: "Center",
className: "fa fa-align-center",
},
{ value: "right", title: "Right", className: "fa fa-align-right" },
];
options.forEach((opt) => {
const button = document.createElement("button");
button.type = "button";
button.className = "horizontal-align-btn";
button.dataset.value = opt.value;
button.title = opt.title || "";
if (opt.className) {
const icon = document.createElement("i");
icon.className = opt.className;
button.appendChild(icon);
} else {
button.textContent = opt.title || opt.value;
}
button.style.cssText = `
min-width: 48px;
height: 40px;
padding: 10px 12px;
border: 2px solid #e5e7eb;
background: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
color: #6b7280;
border-radius: 7px;
`;
button.addEventListener("click", () => {
const selected = gjsEditor.getSelected();
if (!selected) return;
const parent = selected.parent();
if (parent) {
const parentStyles = parent.getStyle();
const parentDisplay = parentStyles.display;
if (parentDisplay !== "flex" && parentDisplay !== "inline-flex") {
parent.addStyle({
display: "flex",
});
}
let justifyContentValue = "flex-start";
if (opt.value === "center") {
justifyContentValue = "center";
} else if (opt.value === "right") {
justifyContentValue = "flex-end";
}
parent.addStyle({
"justify-content": justifyContentValue,
});
}
updateButtonStates();
});
el.appendChild(button);
});
const updateButtonStates = (): void => {
const selected = gjsEditor.getSelected();
if (!selected) return;
const parent = selected.parent();
if (!parent) return;
const parentStyles = parent.getStyle();
const currentValue = parentStyles["justify-content"] || "flex-start";
el.querySelectorAll(".horizontal-align-btn").forEach((button) => {
const btnEl = button as HTMLElement;
const btnValue = btnEl.dataset.value; // left, center, right
let isActive = false;
if (btnValue === 'left' && (currentValue === 'flex-start' || !currentValue)) {
isActive = true;
} else if (btnValue === 'center' && currentValue === 'center') {
isActive = true;
} else if (btnValue === 'right' && currentValue === 'flex-end') {
isActive = true;
}
if (isActive) {
btnEl.style.background = "#6366f1";
btnEl.style.color = "white";
btnEl.style.borderColor = "#6366f1";
btnEl.style.boxShadow = "0 0 0 3px rgba(99, 102, 241, 0.1)";
} else {
btnEl.style.background = "white";
btnEl.style.color = "#6b7280";
btnEl.style.borderColor = "#e5e7eb";
btnEl.style.boxShadow = "none";
}
});
};
setTimeout(updateButtonStates, 0);
gjsEditor.on("component:selected", updateButtonStates);
gjsEditor.on("styleable:change", updateButtonStates);
return el;
},
});
// Vertical Align Selector Type
styleManager.addType("vertical-align-selector", {
create(_args?: any): HTMLElement {
const el = document.createElement("div");
el.className = "vertical-align-selector-container";
el.style.cssText =
"display: flex; gap: 8px; padding: 10px 0; flex-wrap: wrap;";
const options: EnhancedRadioOption[] = [
{ value: "flex-start", title: "Top", className: "fa fa-arrow-up" },
{ value: "center", title: "Middle", className: "fa fa-arrows-v" },
{ value: "flex-end", title: "Bottom", className: "fa fa-arrow-down" },
];
options.forEach((opt) => {
const button = document.createElement("button");
button.type = "button";
button.className = "vertical-align-btn";
button.dataset.value = opt.value;
button.title = opt.title || "";
if (opt.className) {
const icon = document.createElement("i");
icon.className = opt.className;
button.appendChild(icon);
} else {
button.textContent = opt.title || opt.value;
}
button.style.cssText = `
min-width: 48px;
height: 40px;
padding: 10px 12px;
border: 2px solid #e5e7eb;
background: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
color: #6b7280;
border-radius: 7px;
`;
button.addEventListener("click", () => {
const selected = gjsEditor.getSelected();
if (!selected) return;
const parent = selected.parent();
if (parent) {
const parentStyles = parent.getStyle();
const parentDisplay = parentStyles.display;
if (parentDisplay !== "flex" && parentDisplay !== "inline-flex") {
parent.addStyle({
display: "flex",
"flex-direction": "row",
});
}
parent.addStyle({
"align-items": opt.value,
});
}
updateButtonStates();
});
el.appendChild(button);
});
const updateButtonStates = (): void => {
const selected = gjsEditor.getSelected();
if (!selected) return;
const parent = selected.parent();
if(!parent) return;
const styles = parent.getStyle();
const currentValue = styles["align-items"] || "flex-start";
el.querySelectorAll(".vertical-align-btn").forEach((button) => {
const btnValue = (button as HTMLElement).dataset.value;
const isActive =
currentValue === btnValue ||
(btnValue === "flex-start" &&
(!currentValue ||
currentValue === "normal" ||
currentValue === "stretch"));
const btnEl = button as HTMLElement;
if (isActive) {
btnEl.style.background = "#6366f1";
btnEl.style.color = "white";
btnEl.style.borderColor = "#6366f1";
btnEl.style.boxShadow = "0 0 0 3px rgba(99, 102, 241, 0.1)";
} else {
btnEl.style.background = "white";
btnEl.style.color = "#6b7280";
btnEl.style.borderColor = "#e5e7eb";
btnEl.style.boxShadow = "none";
}
});
};
setTimeout(updateButtonStates, 0);
gjsEditor.on("component:selected", updateButtonStates);
gjsEditor.on("styleable:change", updateButtonStates);
return el;
},
});
В моей фактической настройке GrapesJS я создал собственный тип ввода Style Manager:
Код: Выделить всё
styleManager.addType("vertical-align-selector", { ... });
Применяется пользовательский элемент управления:
Код: Выделить всё
display: flex;
align-items: flex-start | center | flex-end;
Сейчас
вертикальное выравнивание работает только в том случае, если родительский элемент становится гибким контейнером , поэтому я заставляю:
Я хочу знать:
Есть ли правильный способ вертикально выровнять компонент GrapesJS без преобразования его родительского элемента во flexbox?
Подробнее здесь:
https://stackoverflow.com/questions/798 ... rent-to-fl
1763288571
Anonymous
Проблема, с которой я столкнулся, связана с вертикальным выравниванием внутри компонента GrapesJS, но проблему можно воспроизвести даже без GrapesJS. Вот небольшой работоспособный пример, показывающий точное поведение: [code] width: 300px; height: 150px; border: 1px solid #ccc; background: #fafafa; "> Content Top Middle Bottom document.querySelectorAll("button").forEach((btn) => { btn.addEventListener("click", () => { const parent = document.getElementById("parent"); // In GrapesJS this is parent.addStyle(...) if (parent.style.display !== "flex") { parent.style.display = "flex"; parent.style.flexDirection = "row"; } parent.style.alignItems = btn.dataset.value; }); }); styleManager.addType("horizontal-align-selector", { create(_args?: any): HTMLElement { const el = document.createElement("div"); el.className = "horizontal-align-selector-container"; el.style.cssText = "display: flex; gap: 8px; padding: 10px 0; flex-wrap: wrap;"; const options: EnhancedRadioOption[] = [ { value: "left", title: "Left", className: "fa fa-align-left" }, { value: "center", title: "Center", className: "fa fa-align-center", }, { value: "right", title: "Right", className: "fa fa-align-right" }, ]; options.forEach((opt) => { const button = document.createElement("button"); button.type = "button"; button.className = "horizontal-align-btn"; button.dataset.value = opt.value; button.title = opt.title || ""; if (opt.className) { const icon = document.createElement("i"); icon.className = opt.className; button.appendChild(icon); } else { button.textContent = opt.title || opt.value; } button.style.cssText = ` min-width: 48px; height: 40px; padding: 10px 12px; border: 2px solid #e5e7eb; background: #ffffff; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; color: #6b7280; border-radius: 7px; `; button.addEventListener("click", () => { const selected = gjsEditor.getSelected(); if (!selected) return; const parent = selected.parent(); if (parent) { const parentStyles = parent.getStyle(); const parentDisplay = parentStyles.display; if (parentDisplay !== "flex" && parentDisplay !== "inline-flex") { parent.addStyle({ display: "flex", }); } let justifyContentValue = "flex-start"; if (opt.value === "center") { justifyContentValue = "center"; } else if (opt.value === "right") { justifyContentValue = "flex-end"; } parent.addStyle({ "justify-content": justifyContentValue, }); } updateButtonStates(); }); el.appendChild(button); }); const updateButtonStates = (): void => { const selected = gjsEditor.getSelected(); if (!selected) return; const parent = selected.parent(); if (!parent) return; const parentStyles = parent.getStyle(); const currentValue = parentStyles["justify-content"] || "flex-start"; el.querySelectorAll(".horizontal-align-btn").forEach((button) => { const btnEl = button as HTMLElement; const btnValue = btnEl.dataset.value; // left, center, right let isActive = false; if (btnValue === 'left' && (currentValue === 'flex-start' || !currentValue)) { isActive = true; } else if (btnValue === 'center' && currentValue === 'center') { isActive = true; } else if (btnValue === 'right' && currentValue === 'flex-end') { isActive = true; } if (isActive) { btnEl.style.background = "#6366f1"; btnEl.style.color = "white"; btnEl.style.borderColor = "#6366f1"; btnEl.style.boxShadow = "0 0 0 3px rgba(99, 102, 241, 0.1)"; } else { btnEl.style.background = "white"; btnEl.style.color = "#6b7280"; btnEl.style.borderColor = "#e5e7eb"; btnEl.style.boxShadow = "none"; } }); }; setTimeout(updateButtonStates, 0); gjsEditor.on("component:selected", updateButtonStates); gjsEditor.on("styleable:change", updateButtonStates); return el; }, }); // Vertical Align Selector Type styleManager.addType("vertical-align-selector", { create(_args?: any): HTMLElement { const el = document.createElement("div"); el.className = "vertical-align-selector-container"; el.style.cssText = "display: flex; gap: 8px; padding: 10px 0; flex-wrap: wrap;"; const options: EnhancedRadioOption[] = [ { value: "flex-start", title: "Top", className: "fa fa-arrow-up" }, { value: "center", title: "Middle", className: "fa fa-arrows-v" }, { value: "flex-end", title: "Bottom", className: "fa fa-arrow-down" }, ]; options.forEach((opt) => { const button = document.createElement("button"); button.type = "button"; button.className = "vertical-align-btn"; button.dataset.value = opt.value; button.title = opt.title || ""; if (opt.className) { const icon = document.createElement("i"); icon.className = opt.className; button.appendChild(icon); } else { button.textContent = opt.title || opt.value; } button.style.cssText = ` min-width: 48px; height: 40px; padding: 10px 12px; border: 2px solid #e5e7eb; background: #ffffff; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; color: #6b7280; border-radius: 7px; `; button.addEventListener("click", () => { const selected = gjsEditor.getSelected(); if (!selected) return; const parent = selected.parent(); if (parent) { const parentStyles = parent.getStyle(); const parentDisplay = parentStyles.display; if (parentDisplay !== "flex" && parentDisplay !== "inline-flex") { parent.addStyle({ display: "flex", "flex-direction": "row", }); } parent.addStyle({ "align-items": opt.value, }); } updateButtonStates(); }); el.appendChild(button); }); const updateButtonStates = (): void => { const selected = gjsEditor.getSelected(); if (!selected) return; const parent = selected.parent(); if(!parent) return; const styles = parent.getStyle(); const currentValue = styles["align-items"] || "flex-start"; el.querySelectorAll(".vertical-align-btn").forEach((button) => { const btnValue = (button as HTMLElement).dataset.value; const isActive = currentValue === btnValue || (btnValue === "flex-start" && (!currentValue || currentValue === "normal" || currentValue === "stretch")); const btnEl = button as HTMLElement; if (isActive) { btnEl.style.background = "#6366f1"; btnEl.style.color = "white"; btnEl.style.borderColor = "#6366f1"; btnEl.style.boxShadow = "0 0 0 3px rgba(99, 102, 241, 0.1)"; } else { btnEl.style.background = "white"; btnEl.style.color = "#6b7280"; btnEl.style.borderColor = "#e5e7eb"; btnEl.style.boxShadow = "none"; } }); }; setTimeout(updateButtonStates, 0); gjsEditor.on("component:selected", updateButtonStates); gjsEditor.on("styleable:change", updateButtonStates); return el; }, }); [/code] В моей фактической настройке GrapesJS я создал собственный тип ввода Style Manager: [code]styleManager.addType("vertical-align-selector", { ... }); [/code] Применяется пользовательский элемент управления: [code]display: flex; align-items: flex-start | center | flex-end; [/code] Сейчас [b]вертикальное выравнивание работает только в том случае, если родительский элемент становится гибким контейнером[/b], поэтому я заставляю: [code]display: flex; [/code] Я хочу знать: [b]Есть ли правильный способ вертикально выровнять компонент GrapesJS без преобразования его родительского элемента во flexbox?[/b] Подробнее здесь: [url]https://stackoverflow.com/questions/79821387/how-to-vertically-align-a-grapesjs-component-without-converting-its-parent-to-fl[/url]