Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: marcj/css-element-queries
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.0.5
Choose a base ref
...
head repository: marcj/css-element-queries
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Oct 18, 2018

  1. don't explode if an old browser can't provide element, computed style…

    …, or style property (#241)
    nskazki authored and marcj committed Oct 18, 2018
    Copy the full SHA
    da5f446 View commit details

Commits on Nov 6, 2018

  1. Added mutationobserver to detect DOM movements and reset sensor accor…

    …dingly.
    
    fixed a bug in Safari where dragdrop doesn't work with huge sensors
    dimensions.
    marcj committed Nov 6, 2018
    Copy the full SHA
    f6144fb View commit details
  2. Copy the full SHA
    5adf8a6 View commit details
  3. bump version

    marcj committed Nov 6, 2018
    Copy the full SHA
    8bc290c View commit details

Commits on Nov 8, 2018

  1. added ResizeCallback to ResizeSensor.d.ts and added static functions … (

    #248)
    
    * added ResizeCallback to ResizeSensor.d.ts and added static functions to ResizeSensor class
    
    * renaming ResizeCallback to ResizeSensorCallback
    
    * added optional type to callback for detach methods on ResizeSensor
    coalman authored and marcj committed Nov 8, 2018
    Copy the full SHA
    9cb667d View commit details

Commits on Nov 9, 2018

  1. Make the MutationObserver of ResizeSensor compatible with new Chrome …

    …version (#249)
    
    The for-in loop over the `NodeList` now goes through the prototype chain, so we check `hasOwnProperty` first
    alubbe authored and marcj committed Nov 9, 2018
    Copy the full SHA
    09d4cf1 View commit details
  2. bump version

    marcj committed Nov 9, 2018
    Copy the full SHA
    31cabb5 View commit details

Commits on May 9, 2019

  1. Copy the full SHA
    7587a65 View commit details
  2. Copy the full SHA
    0cadf8d View commit details
  3. Copy the full SHA
    389ca99 View commit details
  4. Copy the full SHA
    eab5e1b View commit details
  5. bump version

    marcj committed May 9, 2019
    Copy the full SHA
    c4f7808 View commit details
  6. added npmignore file

    marcj committed May 9, 2019
    Copy the full SHA
    5d92e05 View commit details

Commits on Jul 17, 2019

  1. Copy the full SHA
    d1816ad View commit details
  2. bump version

    marcj committed Jul 17, 2019
    Copy the full SHA
    7d0ffa0 View commit details

Commits on Oct 2, 2019

  1. Copy the full SHA
    9fe2fbf View commit details

Commits on Oct 7, 2019

  1. Create FUNDING.yml

    marcj authored Oct 7, 2019
    Copy the full SHA
    4b06648 View commit details

Commits on Nov 22, 2019

  1. cancelAnimationFrame for reset when detach is called (#282)

    In ResizeSensor, we use requestAnimationFrame for reset. However, if an element is added to DOM and removed from DOM very quickly (even before the first reset happens), the reset itself will stuck in an infinite loop. It also causes memory leak because the reset function holds the element forever.
    Ruhao Gao authored and marcj committed Nov 22, 2019
    3
    Copy the full SHA
    1e40612 View commit details
  2. bump version

    marcj committed Nov 22, 2019
    Copy the full SHA
    8f127c7 View commit details

Commits on Jan 3, 2020

  1. Copy the full SHA
    72adc49 View commit details
  2. bump version

    marcj committed Jan 3, 2020
    Copy the full SHA
    df88b1e View commit details

Commits on Apr 13, 2020

  1. Fixed check for canceling last animation frame for invisible check (#289

    )
    
    Co-authored-by: Aleksandar Adamovic <accordia.aleksandaradamovic@outlook.com>
    Acinho and Aleksandar Adamovic authored Apr 13, 2020
    Copy the full SHA
    97da8b7 View commit details
  2. fix invoking element.resizeSensor.resetSensor when it is undefined to…

    … cause js error (#291)
    
    Co-authored-by: akelley <achara.kelley@verizonmedia.com>
    Achara and akelley authored Apr 13, 2020
    Copy the full SHA
    ee21dfe View commit details

Commits on Jul 3, 2020

  1. Add a Size interface to the typings (#293)

    Co-authored-by: Maksym Kobieliev <kobieliev@extedo.com>
    Maximaximum and Maksym Kobieliev authored Jul 3, 2020
    Copy the full SHA
    4eae465 View commit details
Showing with 442 additions and 49 deletions.
  1. +1 −0 .github/FUNDING.yml
  2. +2 −0 .npmignore
  3. +2 −0 css-element-queries.d.ts
  4. +2 −1 package.json
  5. +14 −0 src/ElementQueries.d.ts
  6. +6 −5 src/ElementQueries.js
  7. +35 −3 src/ResizeSensor.d.ts
  8. +135 −38 src/ResizeSensor.js
  9. +31 −2 tests/demo.html
  10. +75 −0 tests/mutation/app.js
  11. +56 −0 tests/mutation/app.ts
  12. +76 −0 tests/mutation/index.html
  13. +7 −0 tsconfig.json
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: marcj
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
tests
2 changes: 2 additions & 0 deletions css-element-queries.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ResizeSensor, ResizeSensorCallback, Size } from "./src/ResizeSensor";
export { ElementQueries } from './src/ElementQueries';
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "css-element-queries",
"version": "1.0.5",
"version": "1.2.3",
"description": "CSS-Element-Queries Polyfill. Proof-of-concept for high-speed element dimension/media queries in valid css.",
"main": "index.js",
"typings": "css-element-queries.d.ts",
"directories": {
"test": "test"
},
14 changes: 14 additions & 0 deletions src/ElementQueries.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export declare class ElementQueries {
/**
* Attaches to DOMLoadContent
*/
static listen(): void;

/**
* Parses all available CSS and attach ResizeSensor to those elements which have rules attached.
* Make sure this is called after 'load' event, because CSS files are not ready when domReady is fired.
*/
static init(): void;
}

export default ElementQueries;
11 changes: 6 additions & 5 deletions src/ElementQueries.js
Original file line number Diff line number Diff line change
@@ -170,13 +170,12 @@
if (!element.elementQueriesSetupInformation) {
element.elementQueriesSetupInformation = new SetupInformation(element, id);
}

if (!element.elementQueriesSensor) {
element.elementQueriesSensor = new ResizeSensor(element, function () {
element.elementQueriesSetupInformation.call();
});
}

element.elementQueriesSetupInformation.call();
}

/**
@@ -409,9 +408,11 @@

document.body.addEventListener(animationStart, function (e) {
var element = e.target;
var styles = window.getComputedStyle(element, null);
var styles = element && window.getComputedStyle(element, null);
var animationName = styles && styles.getPropertyValue('animation-name');
var requiresSetup = animationName && (-1 !== animationName.indexOf('element-queries'));

if (-1 !== styles.getPropertyValue('animation-name').indexOf('element-queries')) {
if (requiresSetup) {
element.elementQueriesSensor = new ResizeSensor(element, function () {
if (element.elementQueriesSetupInformation) {
element.elementQueriesSetupInformation.call();
@@ -439,7 +440,7 @@
for (var i = 0, j = document.styleSheets.length; i < j; i++) {
try {
if (document.styleSheets[i].href && 0 === document.styleSheets[i].href.indexOf('file://')) {
console.log("CssElementQueries: unable to parse local css files, " + document.styleSheets[i].href);
console.warn("CssElementQueries: unable to parse local css files, " + document.styleSheets[i].href);
}

readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText);
38 changes: 35 additions & 3 deletions src/ResizeSensor.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
declare class ResizeSensor {
constructor(element: (Element | Element[]), callback: Function);
detach(callback: Function): void;
export declare interface Size {
width: number;
height: number;
}

export declare type ResizeSensorCallback = (size: Size) => void;

export declare class ResizeSensor {
/**
* Creates a new resize sensor on given elements. The provided callback is called max 1 times per requestAnimationFrame and
* is called initially.
*/
constructor(element: Element | Element[], callback: ResizeSensorCallback);

/**
* Removes the resize sensor, and stops listening to resize events.
*/
detach(callback?: ResizeSensorCallback): void;

/**
* Resets the resize sensors, so for the next element resize is correctly detected. This is rare cases necessary
* when the resize sensor isn't initialised correctly or is in a broken state due to DOM modifications.
*/
reset(): void;

/**
* Removes the resize sensor, and stops listening to resize events.
*/
static detach(element: Element | Element[], callback?: ResizeSensorCallback): void;

/**
* Resets the resize sensors, so for the next element resize is correctly detected. This is rare cases necessary
* when the resize sensor isn't initialised correctly or is in a broken state due to DOM modifications.
*/
static reset(element: Element | Element[]): void;
}

export default ResizeSensor;
173 changes: 135 additions & 38 deletions src/ResizeSensor.js
Original file line number Diff line number Diff line change
@@ -19,14 +19,28 @@
if (typeof window === "undefined") {
return null;
}
// https://github.com/Semantic-Org/Semantic-UI/issues/3855
// https://github.com/marcj/css-element-queries/issues/257
var globalWindow = typeof window != 'undefined' && window.Math == Math
? window
: typeof self != 'undefined' && self.Math == Math
? self
: Function('return this')();
// Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor.
// In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
// would generate too many unnecessary events.
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
var requestAnimationFrame = globalWindow.requestAnimationFrame ||
globalWindow.mozRequestAnimationFrame ||
globalWindow.webkitRequestAnimationFrame ||
function (fn) {
return window.setTimeout(fn, 20);
return globalWindow.setTimeout(fn, 20);
};

var cancelAnimationFrame = globalWindow.cancelAnimationFrame ||
globalWindow.mozCancelAnimationFrame ||
globalWindow.webkitCancelAnimationFrame ||
function (timer) {
globalWindow.clearTimeout(timer);
};

/**
@@ -74,6 +88,18 @@
}
}

/**
* Apply CSS styles to element.
*
* @param {HTMLElement} element
* @param {Object} style
*/
function setStyle(element, style) {
Object.keys(style).forEach(function(key) {
element.style[key] = style[key];
});
}

/**
* Class for dimension change detection.
*
@@ -83,6 +109,9 @@
* @constructor
*/
var ResizeSensor = function(element, callback) {
//Is used when checking in reset() only for invisible elements
var lastAnimationFrameForInvisibleCheck = 0;

/**
*
* @constructor
@@ -131,44 +160,77 @@
element.resizeSensor = document.createElement('div');
element.resizeSensor.dir = 'ltr';
element.resizeSensor.className = 'resize-sensor';
var style = 'position: absolute; left: -10px; top: -10px; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden; max-width: 100%';
var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';

element.resizeSensor.style.cssText = style;
element.resizeSensor.innerHTML =
'<div class="resize-sensor-expand" style="' + style + '">' +
'<div style="' + styleChild + '"></div>' +
'</div>' +
'<div class="resize-sensor-shrink" style="' + style + '">' +
'<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
'</div>';

var style = {
pointerEvents: 'none',
position: 'absolute',
left: '0px',
top: '0px',
right: '0px',
bottom: '0px',
overflow: 'hidden',
zIndex: '-1',
visibility: 'hidden',
maxWidth: '100%'
};
var styleChild = {
position: 'absolute',
left: '0px',
top: '0px',
transition: '0s',
};

setStyle(element.resizeSensor, style);

var expand = document.createElement('div');
expand.className = 'resize-sensor-expand';
setStyle(expand, style);

var expandChild = document.createElement('div');
setStyle(expandChild, styleChild);
expand.appendChild(expandChild);

var shrink = document.createElement('div');
shrink.className = 'resize-sensor-shrink';
setStyle(shrink, style);

var shrinkChild = document.createElement('div');
setStyle(shrinkChild, styleChild);
setStyle(shrinkChild, { width: '200%', height: '200%' });
shrink.appendChild(shrinkChild);

element.resizeSensor.appendChild(expand);
element.resizeSensor.appendChild(shrink);
element.appendChild(element.resizeSensor);

var computedStyle = window.getComputedStyle(element);
var position = computedStyle ? computedStyle.getPropertyValue('position') : null;
if ('absolute' !== position && 'relative' !== position && 'fixed' !== position) {
if ('absolute' !== position && 'relative' !== position && 'fixed' !== position && 'sticky' !== position) {
element.style.position = 'relative';
}

var expand = element.resizeSensor.childNodes[0];
var expandChild = expand.childNodes[0];
var shrink = element.resizeSensor.childNodes[1];
var dirty, rafId;
var dirty = false;

//last request animation frame id used in onscroll event
var rafId = 0;
var size = getElementSize(element);
var lastWidth = size.width;
var lastHeight = size.height;
var lastWidth = 0;
var lastHeight = 0;
var initialHiddenCheck = true;
var lastAnimationFrame = 0;
lastAnimationFrameForInvisibleCheck = 0;

var resetExpandShrink = function () {
expandChild.style.width = '100000px';
expandChild.style.height = '100000px';
var width = element.offsetWidth;
var height = element.offsetHeight;

expandChild.style.width = (width + 10) + 'px';
expandChild.style.height = (height + 10) + 'px';

expand.scrollLeft = 100000;
expand.scrollTop = 100000;
expand.scrollLeft = width + 10;
expand.scrollTop = height + 10;

shrink.scrollLeft = 100000;
shrink.scrollTop = 100000;
shrink.scrollLeft = width + 10;
shrink.scrollTop = height + 10;
};

var reset = function() {
@@ -177,10 +239,9 @@
var invisible = element.offsetWidth === 0 && element.offsetHeight === 0;
if (invisible) {
// Check in next frame
if (!lastAnimationFrame){
lastAnimationFrame = requestAnimationFrame(function(){
lastAnimationFrame = 0;

if (!lastAnimationFrameForInvisibleCheck){
lastAnimationFrameForInvisibleCheck = requestAnimationFrame(function(){
lastAnimationFrameForInvisibleCheck = 0;
reset();
});
}
@@ -231,26 +292,40 @@
addEvent(expand, 'scroll', onScroll);
addEvent(shrink, 'scroll', onScroll);

// Fix for custom Elements
requestAnimationFrame(reset);
// Fix for custom Elements and invisible elements
lastAnimationFrameForInvisibleCheck = requestAnimationFrame(function(){
lastAnimationFrameForInvisibleCheck = 0;
reset();
});
}

forEachElement(element, function(elem){
attachResizeEvent(elem, callback);
});

this.detach = function(ev) {
// clean up the unfinished animation frame to prevent a potential endless requestAnimationFrame of reset
if (lastAnimationFrameForInvisibleCheck) {
cancelAnimationFrame(lastAnimationFrameForInvisibleCheck);
lastAnimationFrameForInvisibleCheck = 0;
}
ResizeSensor.detach(element, ev);
};

this.reset = function() {
element.resizeSensor.resetSensor();
//To prevent invoking element.resizeSensor.resetSensor if it's undefined
if (element.resizeSensor.resetSensor) {
element.resizeSensor.resetSensor();
}
};
};

ResizeSensor.reset = function(element, ev) {
ResizeSensor.reset = function(element) {
forEachElement(element, function(elem){
elem.resizeSensor.resetSensor();
//To prevent invoking element.resizeSensor.resetSensor if it's undefined
if (element.resizeSensor.resetSensor) {
elem.resizeSensor.resetSensor();
}
});
};

@@ -271,6 +346,28 @@
});
};

if (typeof MutationObserver !== "undefined") {
var observer = new MutationObserver(function (mutations) {
for (var i in mutations) {
if (mutations.hasOwnProperty(i)) {
var items = mutations[i].addedNodes;
for (var j = 0; j < items.length; j++) {
if (items[j].resizeSensor) {
ResizeSensor.reset(items[j]);
}
}
}
}
});

document.addEventListener("DOMContentLoaded", function (event) {
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
}

return ResizeSensor;

}));
33 changes: 31 additions & 2 deletions tests/demo.html
Original file line number Diff line number Diff line change
@@ -97,6 +97,15 @@
.example-2[min-width~="400px"] .example-2-box {
background-color: gray;
}

#example-invisible {
display: none;
}

#example-invisible[min-width~="100px"]{
color: red;
font-weight: bold;
}
</style>

<script type="text/javascript">
@@ -257,7 +266,7 @@ <h2>Demo 2</h2>
</div>
</div>
<div class="code html">
<textarea class="html">
<textarea class="html" readonly>
<div class="example-2">
<h2>Demo 2</h2>
<div class="example-2-box">
@@ -373,6 +382,26 @@ <h2>ResizeSensor Demo</h2>
</div>
</div>

<div class="example">
<h2>Invisible Demo</h2>
<div style="height: 80px">
<div class="examplesResizerDemos">
Press button to show.
<div id="example-invisible">
This should be red.
</div>
</div>
</div>
<div class="example-desc">
<button onclick="document.getElementById('example-invisible').style.display = 'block'">Unhide box</button>
<script type="text/javascript">
new ResizeSensor(document.getElementById('example-invisible'), function(d) {
console.log('invisible box changed', d);
});
</script>
</div>
</div>

<div class="example">
<h2>Performance Demo</h2>
<div class="dynamic">
@@ -393,4 +422,4 @@ <h2>Performance Demo</h2>
</div>
</div>
</div>
</body>
</body>
75 changes: 75 additions & 0 deletions tests/mutation/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
var __values = (this && this.__values) || function (o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
};
var e_1, _a, e_2, _b;
var state = {
dragged: null
};
var i = 0;
try {
for (var _c = __values(document.getElementsByClassName('drag')), _d = _c.next(); !_d.done; _d = _c.next()) {
var item = _d.value;
i++;
item.setAttribute('draggable', 'true');
item.setAttribute('id', 'drag-' + i);
(function (element) {
var title = 'Drag me #' + i;
element.setAttribute('data-label', title);
new ResizeSensor(element, function (size) {
element.setAttribute('data-label', title + " (" + size.width + "x" + size.height + ")");
});
})(item);
item.addEventListener('dragstart', function (event) {
state.dragged = event.target;
event.dataTransfer.setData('text', 'thanks firefox');
event.dataTransfer.dropEffect = 'move';
});
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
}
finally { if (e_1) throw e_1.error; }
}
var _loop_1 = function (item) {
(function (element) {
item.addEventListener('drop', function (event) {
event.preventDefault();
item.classList.remove('drag-hover');
state.dragged.parentNode.removeChild(state.dragged);
element.appendChild(state.dragged);
state.dragged = null;
});
})(item);
item.addEventListener('dragleave', function (event) {
item.classList.remove('drag-hover');
});
item.addEventListener('dragover', function (event) {
item.classList.add('drag-hover');
});
item.addEventListener('dragover', function (event) {
event.preventDefault();
});
};
try {
for (var _e = __values(document.getElementsByClassName('container')), _f = _e.next(); !_f.done; _f = _e.next()) {
var item = _f.value;
_loop_1(item);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
}
finally { if (e_2) throw e_2.error; }
}
56 changes: 56 additions & 0 deletions tests/mutation/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
declare const ResizeSensor;

const state: {
dragged: Element
} = {
dragged: null
};

let i = 0;

for (const item of document.getElementsByClassName('drag')) {
i++;
item.setAttribute('draggable', 'true');
item.setAttribute('id', 'drag-' + i);

(element => {
const title = 'Drag me #' + i;
element.setAttribute('data-label', title);

new ResizeSensor(element, (size) => {
element.setAttribute('data-label', `${title} (${size.width}x${size.height})`);
});
})(item);

item.addEventListener('dragstart', (event: DragEvent) => {
state.dragged = <Element>event.target;
event.dataTransfer.setData('text', 'thanks firefox');
event.dataTransfer.dropEffect = 'move';
});
}

for (const item of document.getElementsByClassName('container')) {
(element => {
item.addEventListener('drop', (event) => {
event.preventDefault();
item.classList.remove('drag-hover');

state.dragged.parentNode.removeChild(state.dragged);
element.appendChild(state.dragged);

state.dragged = null;
});
})(item);

item.addEventListener('dragleave', (event) => {
item.classList.remove('drag-hover');
});

item.addEventListener('dragover', (event) => {
item.classList.add('drag-hover');
});

item.addEventListener('dragover', (event) => {
event.preventDefault();
});
}
76 changes: 76 additions & 0 deletions tests/mutation/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<style type="text/css">
body {
background-color: #fafafa;
}

* {
box-sizing: border-box;
}

.container {
margin: 15px;
padding: 15px;
border: 1px solid silver;
border-radius: 3px;
min-height: 250px;
background-color: rgba(255, 255, 255, 0.86);
}

.container.drag-hover {
border: 1px solid red;
background-color: rgba(255, 125, 15, 0.18);
}

@keyframes wrapple {
0%, 100% {
width: 150px;
height: 150px;
}

50% {
width: 120px;
height: 120px;
}
}

.drag {
border: 1px solid silver;
border-radius: 5px;
margin: 15px;
width: 150px;
height: 150px;
background-color: #d1ffff;
animation-name: wrapple;
animation-duration: 4s;
animation-iteration-count: infinite;

display: inline-flex;
justify-content: center;
flex-direction: column;
text-align: center;
}

.drag::after {
font-size: 12px;
content: attr(data-label);
}
</style>

<script src="../../src/ResizeSensor.js"></script>
</head>
<body>
<div id="container1" class="container">
<div class="drag" draggable="true"></div>
<div class="drag"></div>
</div>

<div id="container2" class="container">
<div class="drag"></div>
<div class="drag"></div>
</div>

<script type="application/javascript" src="./app.js"></script>
</body>
7 changes: 7 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"downlevelIteration": true,
"lib" : ["dom","es6","dom.iterable","scripthost", "es2015.iterable", "es2015.collection"]
}
}