Detect support for transition with JavaScript - javascript

I want to serve different javascript files depending on if browser supports CSS3 transition or not. Is there a better way to detect transition support than my code below?
window.onload = function () {
var b = document.body.style;
if(b.MozTransition=='' || b.WebkitTransition=='' || b.OTransition=='' || b.transition=='') {
alert('supported');
} else {
alert('NOT supported')
}
}

I also think including Modernizr is an overkill. The function below should work for any feature.
function detectCSSFeature(featurename){
var feature = false,
domPrefixes = 'Webkit Moz ms O'.split(' '),
elm = document.createElement('div'),
featurenameCapital = null;
featurename = featurename.toLowerCase();
if( elm.style[featurename] !== undefined ) { feature = true; }
if( feature === false ) {
featurenameCapital = featurename.charAt(0).toUpperCase() + featurename.substr(1);
for( var i = 0; i < domPrefixes.length; i++ ) {
if( elm.style[domPrefixes[i] + featurenameCapital ] !== undefined ) {
feature = true;
break;
}
}
}
return feature;
}
var hasCssTransitionSupport = detectCSSFeature("transition");
Inspired by https://developer.mozilla.org/en-US/docs/CSS/Tutorials/Using_CSS_animations/Detecting_CSS_animation_support

Modernizr will detect this for you. Use this link to create a custom download build that only contains CSS3 2D and/or 3D transitions.
Once it's run, you can either test for the csstransitions class on the html tag (CSS), or in JavaScript, test if Modernizr.csstransitions is true.
More docs: http://modernizr.com/docs/#csstransitions

Here is another testing code. Maybe it is an overkill, but the function tries to set the CSS property to DOM object and then read back from it.
Never tested this code on large amount of exotic browsers, but it is safer than just checking for the CSS property availability. Ah, yes, it can distinguish 2D transform support from 3D transform support! Just pass CSS property values you want to test!
The plus of this code is that it detects the vendor prefix supported (if any). Possible return values:
false, when feature unsupported, or
{
vendor: 'moz',
cssStyle: '-moz-transition',
jsStyle: 'MozTransition'
}
when feature supported
/**
* Test for CSS3 feature support. Single-word properties only by now.
* This function is not generic, but it works well for transition and transform at least
*/
testCSSSupport: function (feature, cssTestValue/* optional for transition and transform */) {
var testDiv,
featureCapital = feature.charAt(0).toUpperCase() + feature.substr(1),
vendors = ['', 'webkit', 'moz', 'ms', 'o'],
jsPrefixes = ['', 'Webkit', 'Moz', 'ms', 'O'],
defaultTestValues = {
transition: 'left 2s ease 1s',
transform: 'rotateX(-180deg) translateZ(.5em) scale(0.5)'
// This will test for 3D transform support
// Use other values if you need to test for 2D support only
},
testFunctions = {
transition: function (jsProperty, computed) {
return computed[jsProperty + 'Delay'] === '1s' && computed[jsProperty + 'Duration'] === '2s' && computed[jsProperty + 'Property'] === 'left';
},
transform: function (jsProperty, computed) {
return computed[jsProperty].substr(0, 9) === 'matrix3d(';
}
};
/* test given vendor prefix */
function isStyleSupported(feature, jsPrefixedProperty) {
if (jsPrefixedProperty in testDiv.style) {
var testVal = cssTestValue || defaultTestValues[feature],
testFn = testFunctions[feature];
if (!testVal) {
return false;
}
testDiv.style[jsPrefixedProperty] = testVal;
var computed = window.getComputedStyle(testDiv);
if (testFn) {
return testFn(jsPrefixedProperty, computed);
}
else {
return computed[jsPrefixedProperty] === testVal;
}
}
return false;
}
//Assume browser without getComputedStyle is either IE8 or something even more poor
if (!window.getComputedStyle) {
return false;
}
//Create a div for tests and remove it afterwards
if (!testDiv) {
testDiv = document.createElement('div');
document.body.appendChild(testDiv);
setTimeout(function () {
document.body.removeChild(testDiv);
testDiv = null;
}, 0);
}
var cssPrefixedProperty,
jsPrefixedProperty;
for (var i = 0; i < vendors.length; i++) {
if (i === 0) {
cssPrefixedProperty = feature; //todo: this code now works for single-word features only!
jsPrefixedProperty = feature; //therefore box-sizing -> boxSizing won't work here
}
else {
cssPrefixedProperty = '-' + vendors[i] + '-' + feature;
jsPrefixedProperty = jsPrefixes[i] + featureCapital;
}
if (isStyleSupported(feature, jsPrefixedProperty)) {
return {
vendor: vendors[i],
cssStyle: cssPrefixedProperty,
jsStyle: jsPrefixedProperty
};
}
}
return false;
}
Github: https://github.com/easy-one/CSS3test

if (window.TransitionEvent){
}

With Modernizr 3.0 (alpha), you can generate custom builds locally. This may resolve the aforementioned "overkill" concern - although i'm not entirely clear on that concern in the first place (but i'm assuming it's size). The new api provides a 'build' method, to which you can pass json containing the tests that you would like to include in the build.
I use something like this in my gulp file but gulp is not needed - a simple node script will do.
gulp.task('js:modernizr', function() {
var modConfig = JSON.parse(fs.readFileSync('modernizr-config.json', {
encoding: 'utf8'
}));
modernizr.build(modConfig, function(res) {
fs.writeFileSync('modernizr.js', res);
return true;
});
});
And an example of the 'modernizr-config.json' file would be
{
"classPrefix": "",
"options": [
"addTest",
"atRule",
"domPrefixes",
"hasEvent",
"html5shiv",
"html5printshiv",
"load",
"mq",
"prefixed",
"prefixes",
"prefixedCSS",
"setClasses",
"testAllProps",
"testProp",
"testStyles"
],
"feature-detects": [
"css/transforms",
"css/transforms3d",
"css/transformstylepreserve3d",
"css/transitions",
"touchevents",
"workers/webworkers",
"history"
]
}
The full config file is included in the Modernizr package.
With this approach, you can take advantage of the well maintained Modernizr test suite via package installers and easily add/remove tests as needed. Less tests, smaller file obviously.
The 'setClasses' option will add the related test class to your html but you can also take advantage of the 3.0 async events like so:
Modernizr.on('csstransitions', function(bool) {
if (bool === true) // do transition stuffs
}

Related

Firefox - Event Path is undefined [duplicate]

When I run event.path[n].id in Firefox, I get this error. It works in other browsers.
event.path undefined
The path property of Event objects is non-standard. The standard equivalent is the composedPath method. But it was new when the question was asked (2016); it's well-established as of this update in January 2023.
So you may want to try composedPath and fall back to path (or just use composedPath now it's established):
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
var path = event.composedPath ? event.composedPath() : event.path;
if (path) {
// You got some path information
} else {
// This browser doesn't supply path information
}
Obviously that won't give you path information if the browser doesn't supply it, but it allows for both the old way and the new, standard way, and so will do its best cross-browser.
Example:
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
document.getElementById("target").addEventListener("click", function (e) {
// Just for demonstration purposes
if (e.path) {
if (e.composedPath) {
console.log("Supports `path` and `composedPath`");
} else {
console.log("Supports `path` but not `composedPath`");
}
} else if (e.composedPath) {
console.log("Supports `composedPath` (but not `path`)");
} else {
console.log("Supports neither `path` nor `composedPath`");
}
// Per the above, get the path if we can, first using the standard
// method if possible, falling back to non-standard `path`
var path = event.composedPath ? event.composedPath() : event.path;
// Show it if we got it
if (path) {
console.log("Path (" + path.length + ")");
Array.prototype.forEach.call(path, function(entry) {
console.log(entry === window ? "window" : entry.nodeName);
});
}
});
.as-console-wrapper {
max-height: 100% !important;
}
<div id="target">Click me</div>
According to MDN, all major browsers support composedPath as of January 2023. Chrome (and other Chromium-based browsers) supported both path (it was a Chrome innovation) and composedPath until v109 when path was removed. (The obsolete browsers IE11 and Legacy Edge [Microsoft Edge prior to v79 when it became a Chromium-based browser] didn't support either of them.)
If you ran into a browser that doesn't support either of them, I don't think you can get the path information as of when the event was triggered. You can get the path via e.target.parentNode and each subsequent parentNode, which is usually the same, but of course the point of composedPath is that it's not always the same (if something modifies the DOM after the event was triggered but before your handler got called).
You can create your own composedPath function if it's not implemented in the browser:
function composedPath (el) {
var path = [];
while (el) {
path.push(el);
if (el.tagName === 'HTML') {
path.push(document);
path.push(window);
return path;
}
el = el.parentElement;
}
}
The returned value is equivalent to event.path of Google Chrome.
Example:
document.getElementById('target').addEventListener('click', function(event) {
var path = event.path || (event.composedPath && event.composedPath()) || composedPath(event.target);
});
This function serves as a polyfill for Event.composedPath() or Event.Path
function eventPath(evt) {
var path = (evt.composedPath && evt.composedPath()) || evt.path,
target = evt.target;
if (path != null) {
// Safari doesn't include Window, but it should.
return (path.indexOf(window) < 0) ? path.concat(window) : path;
}
if (target === window) {
return [window];
}
function getParents(node, memo) {
memo = memo || [];
var parentNode = node.parentNode;
if (!parentNode) {
return memo;
}
else {
return getParents(parentNode, memo.concat(parentNode));
}
}
return [target].concat(getParents(target), window);
}
Use composePath() and use a polyfill for IE:
https://gist.github.com/rockinghelvetica/00b9f7b5c97a16d3de75ba99192ff05c
include above file or paste code:
// Event.composedPath
(function(e, d, w) {
if(!e.composedPath) {
e.composedPath = function() {
if (this.path) {
return this.path;
}
var target = this.target;
this.path = [];
while (target.parentNode !== null) {
this.path.push(target);
target = target.parentNode;
}
this.path.push(d, w);
return this.path;
}
}
})(Event.prototype, document, window);
and then use:
var path = event.path || (event.composedPath && event.composedPath());
I had the same issue. I need the name of the HTML element. In Chrome I get the name with path. In Firefox I tried with composedPath, but it returns a different value.
For solving my problem, I used e.target.nodeName. With target function you can retrieve the HTML element in Chrome, Firefox and Safari.
This is my function in Vue.js:
selectFile(e) {
this.nodeNameClicked = e.target.nodeName
if (this.nodeNameClicked === 'FORM' || this.nodeNameClicked === 'INPUT' || this.nodeNameClicked === 'SPAN') {
this.$refs.singlefile.click()
}
}

Is this "DI" structure impossible, or should I look for a bug?

Explanation:
As a personal project, I'm trying to create my own lightweight version of Dependency Injection for JavaScript - Some would probably disagree with calling this DI because it has no interfaces, but I arrived at the conclusion that interfaces were overkill in JS since we can so easily type check. I have looked at the source of Angular, but I just feel like the complexity there may be overkill for my projects, and I'm interested in attempting my own for a learning experience anyway.
Question:
My question is, fundamentally, is the syntax I'm trying to implement impossible or not?
I'll explain my goal for the syntax, then provide the error and code snippet, and below that I'll post the full code.
Goal for Syntax:
I'd like the creation of a component, and injection of dependencies to work like this, where everything is a component, and anything can be a dependency. I created scope with a string path, using "/scopeName/subScopeName:componentName" to select a scope, so that code users can select the scope while defining the component in a simple way, using a ":" to select a component from the scope.
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
/* ...snip - see full code for the process component - snip ... */
JHTML.addComponent('/generate:init', function (jsonInput, process) {
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
The inject function just takes an array of component paths in the order the component's arguments are expected. null can be used to skip, allowing direct argument input instead, as shown above.
I also have something I call hooks, which are components stored in a certain place, and then there's a function returnUserHandle which will return an object consisting of just the hooks, so all of the functions are hidden in closures, and you can feed the code user just the usable methods, clean and easy, and can produce the final product as a library without the wiring, no need for my DI framework as a dependency. Hopefully that makes sense.
Error:
Right now, running the code (which is a very simple library to generate HTML by parsing a JSON structure) I get the error that process is undefined in the line var html = process(jsonInput);. I was having trouble understanding whether this is a fundamental design problem, or just a bug. Maybe this syntax is not possible, I'm hoping you can tell me.
Code:
Here's the code, and a link to the JS Bin.
/* Dependency Injection Framework - viziion.js */
function Viziion(appName) {
if (typeof appName == 'string') {
var that = this;
this.name = appName;
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
if (typeof scopeName == 'string') {
var scopeArray = scopeName.split('/');
var scope = that.scope['/'];
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
scope.subScopes[scopeArray[i]] = {
'subScopes': {},
'components': {}
};
}
}
}
} else {
throw 'Scope path must be a string.';
}
return that;
};
this.addComponent = function(componentName, func) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string1.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
//console.log('yep1');
//console.log(scope.components[scopeName]);
callback(scope.components[scopeName]);
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string2.';
}
};
this.addHook = function(hookName, func) {
if (typeof hookName == 'string') {
that.hooks[hookName] = func;
that.focus = that.hooks[hookName];
} else {
throw 'Hook name must be a string.';
}
return that;
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var args = [];
for (var i = 0; i < dependencyArray.length; i++) {
if (dependencyArray[i] !== null) {
that.returnComponent(dependencyArray[i], function(dependency) {
args.push(dependency);
});
}
}
console.log(that.focus);
that.focus.apply(null, args);
return that;
}
};
this.returnUserHandle = function() {
return that.hooks;
};
} else {
throw 'Viziion name must be a string.';
}
}
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:process', function(children) {
var html = [];
var loop = function() {
for (var i = 0; i < children.length; i++) {
if (children[i].tag) {
html.push('<' + tag + '>');
if (children[i].children) {
loop();
}
html.push('</' + tag + '>');
return html;
} else {
throw '[JHTML] Bad syntax: Tag type is not defined on node.';
}
}
};
}).inject();
JHTML.addComponent('/generate:init', function(jsonInput, process) {
console.log(process);
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
JHTML.addHook('generate', function(jsonInput, init) {
var html = init(jsonInput);
return html;
}).inject([null, '/generate:init']);
handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
var htmlChunk = [{
tag: '!DOCTYPEHTML'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}];
console.log(handle.generate(htmlChunk));
is the syntax I'm trying to implement impossible or not?
It's absolutely possible, and I'm sure with a bit of bugfixing it'd work just fine.
What you're describing is essentially the same as Asynchronous Module Definition (AMD) which is used extensively for handling code dependencies.
Rather than continuing to pursue your own version of the same concept, I recommend that you give requirejs a try and follow the existing standards with your projects.

How to build a namespace-like string using chained variables

This is a strange one, but I'm exploring it to see if it's possible.
Let's say that I have a .NET application where I am using PubSub. I want a way to define the topic string using chained objects (not functions). The goal is to allow me a way of defining strings that lets me to take advantage of Visual Studio's IntelliSense and reduce the likelihood of spelling errors.
Here's an example:
/* Manual way */
var topic = "App.Navigation.CurrentItem"
/* Desired Solution */
// ... define the objects here ...
var topic = App.Navigation.CurrentItem;
console.log(topic); // "App.Navigation.CurrentItem"
var topic2 = App.Footer.CurrentItem;
console.log(topic2); // "App.Footer.CurrentItem"
I'd like each object to be responsible for outputing it's own value, and have the chaining process responsible for joining itself to the previous chained object via a predefined separator (in my case, a period [.]).
I've been playing with JavaScript getter syntax, but I'm curious if there's a better way.
Has anyone done something like this before, and if so, how did you solve it?
You're requirements aren't totally clear to me, but are you looking for something like this?
function namespace(ns) { this._ns = ns; }
namespace.prototype.toString = function() {return this._ns};
namespace.prototype.extend = function(suffix) {
return new namespace(this._ns + "." + suffix)
};
Usage:
App = new namespace('App');
App.Navigation = App.extend('Navigation');
App.Navigation.CurrentItem = App.Navigation.extend('CurrentItem');
console.log(App.Navigation.CurrentItem.toString()); // "App.Navigation.CurrentItem"
This is what I ended up with after reviewing StriplingWarrior's answer:
function Namespace(name, config) {
if (typeof name === "object") {
config = name;
name = null;
}
config = config || {};
this._ns = name;
this.define(config);
}
Namespace.prototype.toString = function() { return this._ns };
Namespace.prototype.define = function(config, base) {
base = base || this;
for (key in config) {
var name = (base._ns) ? base._ns + "." + key : key;
base[key] = new Namespace(name);
base.define(config[key], base[key]);
}
return base;
};
Usage:
var App = new Namespace("App", {
Navigation: {
Items: {
Current: {}
}
},
Content: {},
Footer: {
Items: {
Current: {}
}
}
});
console.log(App.toString()); // App
console.log(App.Navigation.Items.Current.toString()); // App.Navigation.Items.Current
console.log(App.Footer.toString()); // App.Footer
I also wrote a convenience method to reduce the toString():
function NS(namespace) {
return namespace.toString();
}
console.log(NS(App.Navigation.Items.Current));
Thanks again to StriplingWarrior for the the help!

How to get CSS class property in Javascript?

.test {
width:80px;
height:50px;
background-color:#808080;
margin:20px;
}
HTML -
<div class="test">Click Here</div>
In JavaScript i want to get margin:20px
For modern browsers you can use getComputedStyle:
var elem,
style;
elem = document.querySelector('.test');
style = getComputedStyle(elem);
style.marginTop; //`20px`
style.marginRight; //`20px`
style.marginBottom; //`20px`
style.marginLeft; //`20px`
margin is a composite style, and not reliable cross-browser. Each of -top -right, -bottom, and -left should be accessed individually.
fiddle
The accepted answer is the best way to get the computed values. I personally needed the pre-computed value. Say for instance 'height' being set to a 'calc()' value. I wrote the following jQuery function to access the value from the style sheet. This script handles nested 'media' and 'supports' queries, CORS errors, and should provide the final cascaded precomputed value for accessible properties.
$.fn.cssStyle = function() {
var sheets = document.styleSheets, ret = [];
var el = this.get(0);
var q = function(rules){
for (var r in rules) {
var rule = rules[r];
if(rule instanceof CSSMediaRule && window.matchMedia(rule.conditionText).matches){
ret.concat(q(rule.rules || rule.cssRules));
} else if(rule instanceof CSSSupportsRule){
try{
if(CSS.supports(rule.conditionText)){
ret.concat(q(rule.rules || rule.cssRules));
}
} catch (e) {
console.error(e);
}
} else if(rule instanceof CSSStyleRule){
try{
if(el.matches(rule.selectorText)){
ret.push(rule.style);
}
} catch(e){
console.error(e);
}
}
}
};
for (var i in sheets) {
try{
q(sheets[i].rules || sheets[i].cssRules);
} catch(e){
console.error(e);
}
}
return ret.pop();
};
// Your element
console.log($('body').cssStyle().height);
Using jQuery:
$('.class').css( "backgroundColor" );
Another approach (still experimental) could be computedStyleMap:
const computedStyleMap = document.getElementById('my-id').computedStyleMap();
computedStyleMap.get('overflow-x'); // {value: 'scroll'}
computedStyleMap.has('padding-right'); // false
computedStyleMap.entries(); // Iterator {}
I've just released an npm package for this purpose exactly. You can find it here on npm or github:
npm: https://www.npmjs.com/package/stylerjs
github: https://github.com/tjcafferkey/stylerjs
you would use it like so
var styles = styler('.class-name').get(['height', 'width']);
and styles would equal
{height: "50px", width: "50px"}
So you could just get the values like so
var height = styles.height;

open javascript library to do the same thing as symbolset

I was planning on creating my own symbol font for use on my website and I had initially intended on using ligatures and OT but support is spotty at best, and non-existant for older browsers, especially IE (I know, big surprise). I then started taking a close look at how SymbolSet works. It's actually pretty clever and while it would be easy for me to change a couple array variables and be done with it, the javascript is proprietary and I can't use it without permission. I'd love to be able to create my own symbol fonts a la SymbolSet but I'd need an open javascript file to let me do this. Are there any open jQuery plug-ins or javascript libraries that do this?
I have found this http://labs.adamdscott.com/ligatures/ligaturejs.html. Basically it walks the dom, and replaces the known letter combinations with ligatures. I've modified the script to use native ligature support when available:
// ligature.js v1.0
// http://code.google.com/p/ligature-js/
// with modifications by sabof
var ligature = (function() {
var testDiv = document.createElement('div'),
nativeLigatureSupport = testDiv.style.textRendering !== undefined;
if (nativeLigatureSupport) {
return function (node, extended) {
if (!node) {
ligature(document.body, extended);
} else {
node.style.textRendering = 'optimizeLegibility';
}
}
}
return function(node, extended) {
if (!node) {
ligature(document.body, extended);
} else {
if (node.nodeType == 3 && node.parentNode.nodeName != 'SCRIPT') {
node.nodeValue = node.nodeValue
.replace(/ffl/g, 'ffl')
.replace(/ffi/g, 'ffi')
.replace(/fl/g, 'fl')
.replace(/fi/g, 'fi')
.replace(/ff/g, 'ff')
.replace(/ij/g, 'ij')
.replace(/IJ/g, 'IJ');
if (extended) {
node.nodeValue = node.nodeValue
.replace(/ae/g, 'æ')
.replace(/A[Ee]/g, 'Æ')
.replace(/oe/g, 'œ')
.replace(/O[Ee]/g, 'Œ')
.replace(/ue/g, 'ᵫ')
.replace(/st/g, 'st');
}
}
if (node.childNodes) {
for (var i=0; i < node.childNodes.length; i++) {
ligature(node.childNodes.item(i), extended);
}
}
}
};
}());

Categories