Duplicate CSS written under media queries
GrapeJs Editor Initialization Test Block Added Output @artf I am trying to add a custom block and providing HTML/CSS of the block via content attribute. When I drop the block onto the canvas then Media Query CSS is added twice to DOM Element with class gjs-css-rules-480px . It only happens if I provide CSS via content...
Read full answer below ↓Question
Hey @artf Great work on the project!
I noticed something and I am able to reproduce it on my local machine too. Any css written under media queries is being duplicated/applied twice.
Grapesjs Version - 0.14.33
Browser - Version 69.0.3497.100 (Official Build) (64-bit)
OS - Windows 10
Demo - https://grapesjs.com/demo.html
Answers (3)
GrapeJs Editor Initialization
var editor = grapesjs.init({
showOffsets: 1,
noticeOnUnload: 0,
container: '#gjs',
height: '100%',
fromElement: true,
storageManager: { autoload: 0 },
styleManager: {
sectors: [
{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom']
}, {
name: 'Dimension',
open: false,
buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
}, {
name: 'Typography',
open: false,
buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-shadow'],
}, {
name: 'Decorations',
open: false,
buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
}, {
name: 'Extra',
open: false,
buildProps: ['transition', 'perspective', 'transform'],
}, {
name: 'Flex',
open: false,
properties: [
{
name: 'Flex Container',
property: 'display',
type: 'select',
defaults: 'block',
list: [
{ value: 'block', name: 'Disable' },
{ value: 'flex', name: 'Enable' }
],
}, {
name: 'Flex Parent',
property: 'label-parent-flex',
type: 'integer',
}, {
name: 'Direction',
property: 'flex-direction',
type: 'radio',
defaults: 'row',
list: [{
value: 'row',
name: 'Row',
className: 'icons-flex icon-dir-row',
title: 'Row',
}, {
value: 'row-reverse',
name: 'Row reverse',
className: 'icons-flex icon-dir-row-rev',
title: 'Row reverse',
}, {
value: 'column',
name: 'Column',
title: 'Column',
className: 'icons-flex icon-dir-col',
}, {
value: 'column-reverse',
name: 'Column reverse',
title: 'Column reverse',
className: 'icons-flex icon-dir-col-rev',
}],
}, {
name: 'Justify',
property: 'justify-content',
type: 'radio',
defaults: 'flex-start',
list: [{
value: 'flex-start',
className: 'icons-flex icon-just-start',
title: 'Start',
}, {
value: 'flex-end',
title: 'End',
className: 'icons-flex icon-just-end',
}, {
value: 'space-between',
title: 'Space between',
className: 'icons-flex icon-just-sp-bet',
}, {
value: 'space-around',
title: 'Space around',
className: 'icons-flex icon-just-sp-ar',
}, {
value: 'center',
title: 'Center',
className: 'icons-flex icon-just-sp-cent',
}],
}, {
name: 'Align',
property: 'align-items',
type: 'radio',
defaults: 'center',
list: [{
value: 'flex-start',
title: 'Start',
className: 'icons-flex icon-al-start',
}, {
value: 'flex-end',
title: 'End',
className: 'icons-flex icon-al-end',
}, {
value: 'stretch',
title: 'Stretch',
className: 'icons-flex icon-al-str',
}, {
value: 'center',
title: 'Center',
className: 'icons-flex icon-al-center',
}],
}, {
name: 'Flex Children',
property: 'label-parent-flex',
type: 'integer',
}, {
name: 'Order',
property: 'order',
type: 'integer',
defaults: 0,
min: 0
}, {
name: 'Flex',
property: 'flex',
type: 'composite',
properties: [{
name: 'Grow',
property: 'flex-grow',
type: 'integer',
defaults: 0,
min: 0
}, {
name: 'Shrink',
property: 'flex-shrink',
type: 'integer',
defaults: 0,
min: 0
}, {
name: 'Basis',
property: 'flex-basis',
type: 'integer',
units: ['px', '%', ''],
unit: '',
defaults: 'auto',
}],
}, {
name: 'Align',
property: 'align-self',
type: 'radio',
defaults: 'auto',
list: [{
value: 'auto',
name: 'Auto',
}, {
value: 'flex-start',
title: 'Start',
className: 'icons-flex icon-al-start',
}, {
value: 'flex-end',
title: 'End',
className: 'icons-flex icon-al-end',
}, {
value: 'stretch',
title: 'Stretch',
className: 'icons-flex icon-al-str',
}, {
value: 'center',
title: 'Center',
className: 'icons-flex icon-al-center',
}],
}
]
}
]
}
});
Test Block Added
editor.on('load', function () {
var $ = grapesjs.$;
var pn = editor.Panels;
// Load and show settings and style manager
var openTmBtn = pn.getButton('views', 'open-tm');
openTmBtn && openTmBtn.set('active', 1);
var openSm = pn.getButton('views', 'open-sm');
openSm && openSm.set('active', 1);
// Add Settings Sector
var traitsSector = $('<div class="gjs-sm-sector no-select">' +
'<div class="gjs-sm-title"><span class="icon-settings fa fa-cog"></span> Settings</div>' +
'<div class="gjs-sm-properties" style="display: none;"></div></div>');
var traitsProps = traitsSector.find('.gjs-sm-properties');
traitsProps.append($('.gjs-trt-traits'));
$('.gjs-sm-sectors').before(traitsSector);
traitsSector.find('.gjs-sm-title').on('click', function () {
var traitStyle = traitsProps.get(0).style;
var hidden = traitStyle.display == 'none';
if (hidden) {
traitStyle.display = 'block';
} else {
traitStyle.display = 'none';
}
});
// Test Block
editor.BlockManager.add('test', {
label: 'test',
category: 'Basic',
attributes: { class: 'fa fa-header' },
content: `
<header data-gjs-type="default" data-gjs-droppable="true" class="custom-header">
<p>Test</p>
</header>
<style>
.custom-header {
width: 100%;
height: 70px;
background-color: #414141;
padding: 20px;
color: #fff;
}
@media (max-width: 480px){
.custom-header {
background-color: red;
}
}
</style>
`
});
})
Output
@artf I am trying to add a custom block and providing HTML/CSS of the block via content attribute. When I drop the block onto the canvas then Media Query CSS is added twice to DOM Element with class gjs-css-rules-480px . It only happens if I provide CSS via content attribute. If I added any CSS using the StyleManager then it is rendered only once. Seems like issue with parsing CSS when initializing any block.
Link to GrapeJS I am fetching - https://grapesjs.com/js/grapes.min.js?v0.14.33
In my debugging (might be wrong), in the file : ./src/css_composer/index.js
When a rule is added
/**
* Add new rule to the collection, if not yet exists with the same selectors
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} opts Other options for the rule
* @return {Model}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.add([sel1, sel2], 'hover');
* rule.set('style', {
* width: '100px',
* color: '#fff',
* });
* */
add: function add(selectors, state, width) {
var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var s = state || '';
var w = width || '';
var opt = _extends({}, opts);
var rule = this.get(selectors, s, w, opt);
// do not create rules that were found before
// unless this is an at-rule, for which multiple declarations
// make sense (e.g. multiple `@font-type`s)
if (rule && rule.config && !rule.config.atRuleType) {
return rule;
} else {
opt.state = s;
opt.mediaText = w;
opt.selectors = '';
rule = new CssRule(opt, c);
rule.get('selectors').add(selectors);
rules.add(rule);
return rule;
}
},
/**
* Get the rule
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props
* @return {Model|null}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.get([sel1, sel2], 'hover');
* // Update the style
* rule.set('style', {
* width: '300px',
* color: '#000',
* });
* */
get: function get(selectors, state, width, ruleProps) {
var rule = null;
rules.each(function (m) {
if (rule) return;
if (m.compare(selectors, state, width, ruleProps)) rule = m;
});
return rule;
},
The rule is matched against existing rules, if not present then added else updated. Somehow, in the compare function it is not able to find the rule and which leads to adding it twice!
Thanks Yomesh, I'm investigating on this
Related Questions and Answers
Continue research with similar issue discussions.
Issue #1506
Css Media Query Issue
Hello @artf There is an issue on demo page as well as library in media query. Issue is that if user update style on mobile view first and t...
Issue #896
[BUG] Media query rules are overridden by class rules in canvas
Hi @artf , I've noticed an issue while testing one of my templates using different device configurations that supposed to trigger media que...
Issue #1319
BUG Box shadow - not working in the demo too
I have noticed this too late, a few days after finishing my project. I wanna cry :( To reproduce the steps: 1- Go to the demo and select an...
Issue #2044
Npm start to develop on local server didn't work
Did the procedure to start the local server using dev branch as described on README, it worked on the console, but fails on chrome and the...
Paid Plugins That Match This Issue
Curated by issue keywords and label relevance to help you ship faster.
Loading paid plugin recommendations...
Check the open-source GrapesJS plugins on GitHub or run a quick search in our free catalog.
Browse free plugins →Premium plugins ship with support, regular updates, and production-ready features — save days of integration work.
Browse premium plugins →Related tutorials
In-depth guides on the same topic.
Tutorial
How to Build a Production GrapesJS Editor: The Complete Walkthrough of Brief, Preset, Plugins, and Services
A complete walkthrough of building a production GrapesJS editor: how to choose a preset, pick plugins, and scope setup services without burning a sprint.
Tutorial
GrapesJS Inline RTE Plugins Update: CKEditor 5 v0.1.4 and Froala Inline Text Editor
CKEditor 5 Inline for GrapesJS v0.1.4 fixes Studio SDK toolbar clipping, iframe injection and link balloon bugs. Compare with Froala Inline — both $69.
Tutorial
Embed GrapesJS in Your SaaS: A Weekend Guide
Embed GrapesJS in your SaaS and ship a white-label page builder over a weekend. Honest tradeoffs, real code, and the plugins that close the UX gap.
Browse Plugin Categories
Jump directly to plugin category pages on the marketplace.