diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7e45dcf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: marcj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..4f29079 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +.idea +tests diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4de61dc --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Marc J. Schmidt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index a92b0fc..61c88f5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -CSS Element Queries -=================== +# CSS Element Queries -Element Queries is a polyfill adding support for element based media-queries to all new browsers (incl. IE8+). + +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/marcj/css-element-queries?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +Element Queries is a polyfill adding support for element based media-queries to all new browsers (incl. IE7+). It allows not only to define media-queries based on window-size but also adds 'media-queries' functionality depending on element (any selector supported) size while not causing performance lags due to event based implementation. @@ -9,47 +11,63 @@ It's a proof-of-concept event-based CSS element dimension query with valid CSS s Features: - - no performance issues - - no interval/timeout detection. Truly event-based + - no performance issues since it listens only on size changes of elements that have element query rules defined through css. Other element query polifills only listen on `window.onresize` which causes performance issues and allows only to detect changes via window.resize event and not inside layout changes like css3 animation, :hover, DOM changes etc. + - no interval/timeout detection. Truly event-based through integrated ResizeSensor class. + - automatically discovers new DOM elements. No need to call javascript manually. - no CSS modifications. Valid CSS Syntax - - all CSS selectors available. Uses regular attribute selector - - supports and tested in webkit, gecko and IE(8/9/10). + - all CSS selectors available. Uses regular attribute selector. No need to write rules in HTML/JS. + - supports and tested in webkit, gecko and IE(10+) - `min-width`, `min-height`, `max-width` and `max-height` are supported so far - works with any layout modifications: HTML (innerHTML etc), inline styles, DOM mutation, CSS3 transitions, fluid layout changes (also percent changes), pseudo classes (:hover etc.), window resizes and more - no Javascript-Framework dependency (works with jQuery, Mootools, etc.) + - Works beautiful for responsive images without FOUC More demos and information: http://marcj.github.io/css-element-queries/ -Example -------- +## Examples + +### Element Query ```css -.widget-name { - padding: 25px; +.widget-name h2 { + font-size: 12px; } -.widget-name[max-width="200px"] { - padding: 0; + +.widget-name[min-width~="400px"] h2 { + font-size: 18px; } -.widget-name[min-width="500px"] { + +.widget-name[min-width~="600px"] h2 { padding: 55px; + text-align: center; + font-size: 24px; } -/* responsive images /* -.responsive-image img { - width: 100%; +.widget-name[min-width~="700px"] h2 { + font-size: 34px; + color: red; } +``` -.responsive-image[max-width^='400px'] img { - content: url(demo/image-400px.jpg); -} +As you can see we use the `~=` [attribute selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors). +Since this css-element-queries polyfill adds new element attributes on the DOM element +(`
`) depending on your actual CSS and element's dimension, +you should always use this attribute selector (especially if you have several element query rules on the same element). -.responsive-image[max-width^='1000px'] img { - content: url(demo/image-1000px.jpg); -} +```html +
+

Element responsiveness FTW!

+
+``` -.responsive-image[min-width='1000px'] img { - content: url(demo/image-full.jpg); -} +### Responsive image + +```html +
+ + + +
``` Include the javascript files at the bottom and you're good to go. No custom javascript calls needed. @@ -59,15 +77,36 @@ Include the javascript files at the bottom and you're good to go. No custom java ``` -Issues ------- +## See it in action: + +Here live http://marcj.github.io/css-element-queries/. + +![Demo](http://marcj.github.io/css-element-queries/images/css-element-queries-demo.gif) + - - So far does not work on `img` tags. Wrapping with a `div` works fine though (See demo). - - [only non-IE]: Adds additional hidden element into selected target element and forces target element to be relative or absolute. - +## Module Loader + +If you're using a module loader you need to trigger the event listening or initialization yourself: + +```javascript +var ElementQueries = require('css-element-queries/src/ElementQueries'); + + //attaches to DOMLoadContent +ElementQueries.listen(); + +//or if you want to trigger it yourself. +// Parse 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. +ElementQueries.init(); +``` -Event-Based resize detection inspired by [backalleycoder.com](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/) <3 +## Issues + - So far does not work on `img` and other elements that can't contain other elements. Wrapping with a `div` works fine though (See demo). + - Adds additional hidden elements into selected target element and forces target element to be relative or absolute. + - Local stylesheets do not work (using `file://` protocol). + - If you have rules on an element that has a css animation, also add `element-queries`. E.g. `.widget-name { animation: 2sec my-animation, 1s element-queries;}`. We use this to detect new added DOM elements automatically. -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/marcj/css-element-queries/trend.png)](https://bitdeli.com/free "Bitdeli Badge") +## License +MIT license. Copyright [Marc J. Schmidt](https://twitter.com/MarcJSchmidt). diff --git a/css-element-queries.d.ts b/css-element-queries.d.ts new file mode 100644 index 0000000..3fe0706 --- /dev/null +++ b/css-element-queries.d.ts @@ -0,0 +1,2 @@ +export { ResizeSensor, ResizeSensorCallback, Size } from "./src/ResizeSensor"; +export { ElementQueries } from './src/ElementQueries'; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..6f5145b --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +module.exports = { + ResizeSensor: require('./src/ResizeSensor'), + ElementQueries: require('./src/ElementQueries') +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..964230f --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "css-element-queries", + "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" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@github.com:marcj/css-element-queries.git" + }, + "author": "Marc J. Schmidt", + "license": "MIT", + "bugs": { + "url": "https://github.com/marcj/css-element-queries/issues" + }, + "homepage": "https://github.com/marcj/css-element-queries", + "devDependencies": { + "grunt": "^0.4.5", + "grunt-bump": "^0.3.1" + } +} diff --git a/src/ElementQueries.d.ts b/src/ElementQueries.d.ts new file mode 100644 index 0000000..00c5d93 --- /dev/null +++ b/src/ElementQueries.d.ts @@ -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; diff --git a/src/ElementQueries.js b/src/ElementQueries.js old mode 100644 new mode 100755 index 2efe47c..4fe4298 --- a/src/ElementQueries.js +++ b/src/ElementQueries.js @@ -1,11 +1,36 @@ -; -(function() { +'use strict'; + +/** + * Copyright Marc J. Schmidt. See the LICENSE file at the top-level + * directory of this distribution and at + * https://github.com/marcj/css-element-queries/blob/master/LICENSE. + */ +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define(['./ResizeSensor.js'], factory); + } else if (typeof exports === "object") { + module.exports = factory(require('./ResizeSensor.js')); + } else { + root.ElementQueries = factory(root.ResizeSensor); + root.ElementQueries.listen(); + } +}(typeof window !== 'undefined' ? window : this, function (ResizeSensor) { + /** * * @type {Function} * @constructor */ - var ElementQueries = this.ElementQueries = function() { + var ElementQueries = function () { + // + + + + + +
+

+

Examples +
+ Drag the gray line at the right to see it in action. +
+

+
+
+
+

Responsive Text

+
+
+
+

Element responsiveness FTW!

+
+ + + + +
+

Element detachable

+
+ + + +
+

Dynamic element {{id}}

+
+ + +
+
+
+ +
+
+ +
+
+
+
+

Responsive Image

+
+
+
+ + + +
+ + The image above has a default of 700px. Shrink or expend the container too see responsive image + working. + Thanks @placehold.it +
+
+
+ +
+
+
+
+

Responsive Widget

+
+
+

Demo 1

+ This is content from the first responsive demo without media queries. It uses the element + queries + provided by this library. +
+
+ +
+ +
+
+ +
+
+
+ +
+

Responsive Layout

+
+
+

Demo 2

+
+ Box +
+
+ First 1/2 box +
+
+ Second 1/2 box +
+
+
+
+
+ +
+
+ +
+
+
+
+

Responsive Animation

+
+
+

Demo 3 - width + +

+
+ This box is animated through css transitions. + We attached a resize-listener to this box. See below. +
+
+ No changes. +
+
+
+
+
+

Demo 4 - height + +

+
+ This box is animated through css transitions. + We attached a resize-listener to this box. See below. +
+
+ No changes. +
+
+
+
+ +
+

ResizeSensor Demo

+
+
+
+ 0 changes +
+
+
+
+ CSS-Element-Queries comes with a Javascript ResizeSensor class you can use in Javascript directly. +
+ +
+
+
+ +
+

Invisible Demo

+
+
+ Press button to show. +
+ This should be red. +
+
+
+
+ + +
+
+ +
+

Performance Demo

+
+
+ Performance Test: + + + + + + + +
+
+
+
+
+
+
+
+ diff --git a/tests/demo.js b/tests/demo.js new file mode 100644 index 0000000..e4899a1 --- /dev/null +++ b/tests/demo.js @@ -0,0 +1,135 @@ +$(document).ready(function () { + console.log('ready'); + + // $("textarea.html").each(function(idx, textarea) { + // CodeMirror.fromTextArea(textarea, { + // lineNumbers: true, + // mode: "htmlmixed", + // readOnly: true + // }); + // }); + // + // $("textarea.css").each(function(idx, textarea) { + // CodeMirror.fromTextArea(textarea, { + // lineNumbers: true, + // mode: "css", + // readOnly: true + // }); + // }); + // + // $("textarea.javascript").each(function(idx, textarea) { + // CodeMirror.fromTextArea(textarea, { + // lineNumbers: true, + // mode: "javascript", + // readOnly: true + // }); + // }); +}); + +function ResizerDemo(element) { + element = $(element); + var handler = $('
'); + var info = $('
'); + + element.append(handler); + element.append(info); + + var hammer = new Hammer(element[0], {recognizers: [ + [Hammer.Pan, { threshold: 0}] + ]}); + + var startWidth; + element.on('mousedown', function(e){ + e.preventDefault(); + }); + hammer.on('panstart', function(e) { + startWidth = element[0].clientWidth; + }); + + hammer.on('panmove', function(e) { + element[0].style.width = (startWidth + e.deltaX) + 'px'; + info.html(element[0].clientWidth + 'px x ' + element[0].clientHeight + 'px'); + }) +} + +$( document ).ready(function(){ + $('.examplesResizerDemos').each(function(idx, element){ + new ResizerDemo(element); + }); + + perfTest(); + example3(); + example4(); + example5(); +}); + +function perfTest(){ + var container = $('#dynamicContainer'); + var dynamicCount = $('#dynamicCount'); + var dynamicCounter = $('#dynamicCounter'); + + window.detachDynamic = function() { + container.children().each(function(idx, element) { + ResizeSensor.detach(element); + }); + }; + + window.removeDynamic = function() { + container.html(''); + }; + + window.addDynamic = function() { + container.html(''); + var i = 0, to = dynamicCount.val(), div, counter = 0; + for (; i < to; i++) { + div = $('
#'+i+'
'); + container.append(div); + + new ResizeSensor(div, function(){ + counter++; + dynamicCounter.html(counter + ' changes.'); + }); + } + } +} + +function example3(){ + var logger = $('#example-3-log'); + var box = $('#example-3-box'); + + $('#startStop3').on('click', function(){ + if (box.hasClass('example-3-box-start')) { + box.removeClass('example-3-box-start'); + } else { + box.addClass('example-3-box-start'); + } + }); + new ResizeSensor(box, function(el){ + logger.html('Changed to ' + box[0].clientWidth+'px width.'); + }); + +} + +function example4(){ + var logger = $('#example-4-log'); + var box = $('#example-4-box'); + + $('#startStop4').on('click', function(){ + if (box.hasClass('example-4-box-start')) { + box.removeClass('example-4-box-start'); + } else { + box.addClass('example-4-box-start'); + } + }); + new ResizeSensor(box, function(){ + logger.html('Changed to ' + box[0].clientHeight+'px height.'); + }); +} + +function example5(){ + var box = $('#example-5'); + var changed = 0; + new ResizeSensor(box.parent(), function(){ + box[0].innerHTML = (++changed) + ' changes. ' + box.parent()[0].clientWidth+'px/'+box.parent()[0].clientHeight+'px'; + }); +} \ No newline at end of file diff --git a/tests/late-trigger.html b/tests/late-trigger.html new file mode 100644 index 0000000..71a2b17 --- /dev/null +++ b/tests/late-trigger.html @@ -0,0 +1,48 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/mutation/app.js b/tests/mutation/app.js new file mode 100644 index 0000000..2100612 --- /dev/null +++ b/tests/mutation/app.js @@ -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; } +} diff --git a/tests/mutation/app.ts b/tests/mutation/app.ts new file mode 100644 index 0000000..15974f7 --- /dev/null +++ b/tests/mutation/app.ts @@ -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 = 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(); + }); +} diff --git a/tests/mutation/index.html b/tests/mutation/index.html new file mode 100644 index 0000000..6d2ddce --- /dev/null +++ b/tests/mutation/index.html @@ -0,0 +1,76 @@ + + + + + + + + +
+
+
+
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7b56f0e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "target": "es5", + "downlevelIteration": true, + "lib" : ["dom","es6","dom.iterable","scripthost", "es2015.iterable", "es2015.collection"] + } +} \ No newline at end of file