Easy Scroll-Based Animations using CSS Custom Properties - javascript

Now that support for custom css properties is becoming widespread, I had in mind to use them to simplify the creation of scroll-based animations. In Javascript, I'm using style.setProperty('--customProperty', value) to adjust the custom properties on specific elements as the user scrolls.
It works beautifully in Chrome, Firefox, and Safari 10.
But, in Safari 9.1 (which does support custom properties), I can only set the property once. After having been set, it will not update to a new value.
I've got it all in CodePen: https://codepen.io/kirkhadden/pen/JJbXmE/
// Have we scrolled since the last frame?
if (position != wrapper[0].scrollpos) {
// Keeps updating accurately every frame:
window.log.text(position);
// Only happens on the first frame:
wrapper[0].style.setProperty('--scrollpos', position+'px', '');
wrapper[0].scrollpos = position;
} else { // No Change
return false;
}
I can't find any information or even mention of this behavior. I've tested other, simpler uses of style.setProperty() in Safari, and I continue to find that once a property is set, Safari won't update the same property, even if I try to remove the property first.
Is this a bug in Safari 9.1? Is there a work-around? Is there another way to use javascript to set css variables?
Update
So, instead of style.setProperty, I could instead use jQuery's .attr() method to set the property. It's not ideal, since that will overwrite any other style properties, but it works for this.
The bigger problem is that this whole solution is based on the idea of setting ordinary css animations on all my animated elements, but setting the play-state to 'paused', and then using javascript to manipulate the animation-delay according to the scroll position. This allows me to take advantage of inheritance to animate lots of things with minimal DOM manipulation.
Once again, Safari 9.1 is the road block, since it appears that unlike Chrome or Firefox, if the play-state is 'paused', Safari does not start the animation at all, and ignores the animation-delay.

You can try to polyfill CSS variables via JavaScript
For example:
let variables =
{
color: "red",
border: "2px solid blue"
}
// Get STYLE tag
let style = document.getElementById('custom');
// Save its original text
style.dataset.source = style.innerHTML;
function updateStyle()
{
// Replace variables names with their values
style.innerHTML = style.dataset.source.replace(/#(\w+)/g, function(match, name)
{
return variables[name];
});
}
updateStyle();
Now you can use #variableName in your CSS and it'll be replaced with value of variables.variableName
Fiddle: https://jsfiddle.net/JacobDesight/v7mqps84/
#EDIT
You can even create a function for settings variables:
function setProperty(name, value)
{
variables[name] = value;
updateStyle();
}
Now you just simply do something like:
setProperty('color', 'green');
And it will automatically update styles.
Fiddle: https://jsfiddle.net/JacobDesight/v7mqps84/1/

Related

Why jQuery cannot modify svg element width/height tag attributes, but works fine with other attributes?

I have ran into extremely suprising behavior of some older jQuery that does not allow to modify svg element width or height attributes (not styles). It just does not modify them and always silently fails.
It works fine with attributes like mywidth, but not with width.
Note that is related to some very old jQuery version (v1.4.3), but still I am just curious about reasoning behind that.
The setup is simple:
<svg id='mysvg'></svg>
and JS code:
$('#mysvg').attr('width','100');
does nothing, where:
$('#mysvg').attr('mywidth','100');
works fine.
I was supposing some other scripts overrides the changes and tried to affect the attribute after a timeout or on click, but behavior is still the same.
Also I've tried to put breakpoint on attribute modification in Chrome, but it also does not work, it triggers for .attr('mywidht','100') but does not for .attr('width','100). It never generates a console error, always fails silently.
Is there any historical reason or unfamous bug that prevents modifying width and height attribute of svg HTML tag?
Note that with some custom tag like <mysvg> it works also fine and adds width attribute.
JSFiddle reproducing it: https://jsfiddle.net/d8703afx/
Looks like the attr('width') returns some "special" [object SVGAnimatedLength]
That $("#mysvg").attr('width') is returning an SVGAnimatedLength object is actually a good hint. The function does not return the attribute, as this DOM method would:
document.querySelector("#mysvg").getAttribute('width')
but the property
document.querySelector("#mysvg").width
which, for SVG elements that implement that property is an object of type SVGAnimatedLength with roughly this structure:
{
baseVal: {
unitType: number,
value: number,
valueInSpecifiedUnits: number,
valueAsString: number
},
animVal: { /* etc */ }
}
The difference to HTML width properties is that SVG provides for animated values and different unit identifiers.
So probably jQuery, when executing .attr('width', value) tries also not to set the attribute, but the width property to a number, which would be correct for HTML elements. But for SVG it must fail, as the object is readonly.
You can simply set the attribute with the DOM method
document.querySelector("#mysvg").setAttribute('width', 100);
The jquery version you are using (1.4.3) was released in 2010, when the up to date IE was still IE8, and we still had to support things like IE6, y'know the word "HTML5" was a bit like "flying car" at this time...
You should not use such an old version of this library nowadays.
Many bugs, and new features have been fixed/added in newer versions.
E.g a better support for SVG manipulation, which at this time was still an "upcoming" technology.
So by using an up-to-date jQuery version, (or a slightly newer one) you'll be able to do what you want:
$("#mysvg").attr('xxwidth', '333');
$("#info").append("The xxwidth is: " + $("#mysvg").attr('xxwidth') + '<br>');
$("#mysvg").attr('width', '333');
$("#info").append("The width is: " + $("#mysvg").attr('width'));
<!-- not even up to date... current is 3.3.1 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg id="mysvg"></svg><br>
<span id="info"></span>

JS style webkit transform not appearing in inline style

When setting multiple transform properties on DOM element style, the browser is not showing all set properties (both -webkit-, ms, and regular) - for example :
element.style.tranform = 'rotate(90deg)';
element.style.webkitTranform = 'rotate(90deg)';
element.style.msTranform = 'rotate(90deg)';
will produce the following inline style attribute
<div style="transform:rotate(90deg)" ></div>;
This means that the browser is not settings ALL of the properties, instead it is taking only one of them.
This is a problem, because we have to send the HTML as is to the server side in order to produce PDF. so, instead we have found a workaround which works (kind of) - using element.setAttribute('style','transform...-webkit- etc...');
The above approach works, but we have to reconstruct the style attribute each time, and the transform must be applied only AFTER all of the other properties have been set, which is not so elegant.
Does anyone knows how to work around this?
Thanks in advance!
I think you've already found your workaround: Using setAttribute. It's not pretty, but with prefixed properties, you can't trust the HTML from browser X will work in browser Y, since there's no reason browser X would include invalid (from its perspective) properties when generating the text for the style attribute's value.
With your workaround, you'll need to be careful of at least two things:
That you test carefully in your target browsers. I think all browsers faithfully retain what you set with setAttribute (subject to #2 below), but...test. :-)
Be sure nothing in your code sets a style via the style object after you've done the setAttribute thing, since that will make the browser drop the (from its perspective) invalid style text. For instance, on Chrome, Firefox, Edge and Safari (at least), the following drops the invalid foo: bar style when I set fontWeight:
var element = document.getElementById("element");
element.setAttribute("style", "foo: bar");
console.log("before using style:", element.getAttribute("style"));
element.style.fontWeight = "bold";
console.log("after using style: ", element.getAttribute("style"));
<div id="element"></div>

Vanilla javascript ignoring first tab/click

I have a slide in menu using vanilla javascript for use on phones, but so far all my tests have resulted in the mobile browsers ignoring the first tap (have tried both touchstart & click as events). Starting with the second tap it works beautifully with each and every subsequent tap.
As opening & closing the menu is the only javascript function on the pages, I don't want to load a huge library, I want to keep it simple and small. My code is below:
var b = document.getElementById('menubtn');
b.addEventListener('touchstart', function (e) {
var n = document.getElementById('nav');
var ns = n.style.left;
if (ns == "-600px") {
n.style.left = "0px";
} else {
n.style.left = "-600px";
}
e.preventDefault();
});
Any ways to easily eliminate this need for double clicking the first time?
In the fwiw dept, it is a responsive design, with the nav menu in a column on big screens and a slide in on phones.
Edit: Solved the issue
Following through on Matt Styles comment, I tried using classList.toggle and it solved the issue. The final version is below:
var b = document.getElementById('menubtn');
var n = document.getElementById('nav');
b.addEventListener('touchstart', function () {
n.classList.toggle('shwmenu');
setTimeout(function () {
b.classList.toggle('shwmenu');
}, 500);
});
I added the delayed menubtn code to toggle the icon between closed and open states.
The behaviour you describe could be caused by the following:
In your JS you try to implement some kind of On-Off toggle for your nav element, differentiated on the left CSS property value, with -600 representing the off value and 0 representing the on value.
As you said, toggling between those states seems to work fine, but what happens when your element is NOT initialized on exactly -600? Then on your first tap you will always run into your else clause and set it to -600 first, showing effectively no visual effect on your first tap, just as you describe.
For an instant test, just swap the if-else clause around to turn it on when style.left is not -600 and then maybe work up towards a more dynamic differentiation between your states ;)
I think this is because element.style.left is empty even if you have set left property in your stylesheet. You can use element.offsetLeft instead. Please see here to how it works.
HTMLElement.style - MDN
The style property is not useful for learning about the element's style in general, since it represents only the CSS declarations set in the element's inline style attribute, not those that come from style rules elsewhere, such as style rules in the <head> section, or external style sheets. To get the values of all CSS properties for an element you should use window.getComputedStyle() instead.

jQuery $().css("content") returning the string "normal" in IE9

I'm using the CSS content attribute to pass some values from my LESS stylesheet to JavaScript (to use some colors defined in LESS in Canvas elements).
To make my life easier I decided to place these values in a easy way to parse them in JavaScript.
LESS code:
div#colorChart-critical {
content:'#{critical-highest},#{critical-veryhigh},#{critical-high},#{critical-low},#{critical-medium},#{critical-verylow}';
}
which when compiled brings the following CSS:
div#colorChart-critical6 {
content: '#ff0000,#ff7200,#fffc00,#0000ff,#a200ff,#00ff00';
}
Then I try to read them using jQuery:
$("div#colorChart-critical").css("content").split(",");
The problem is that in IE9 calling $("div#colorChart-critical").css("content") is returning the string "normal" for some reason. Opera, Firefox, Safari and Chrome works fine.
Why does this happen in IE9?
Any work-around this issue on IE9? If not any other CSS atribute I can put random texts in?
I could use something like:
background: url(#ff0000,#ff7200,#fffc00,#0000ff,#a200ff,#00ff00);
But this would generate errors on the console.
It's because content as defined in CSS2.1 doesn't work on elements, only on the :before and :after pseudo-elements. IE9 is simply following the CSS2.1 spec here, which mandates that content on elements be computed to normal, always.
I don't know why other browsers would return the value you have defined, especially considering that .css() makes use of getComputedStyle() on those browsers. If they're implementing CSS2.1 content, then they're violating CSS2.1 by not computing the value to normal. If they're preparing for a late CSS3 implementation, whatever that may be, then it would make sense that they implement it on actual elements somehow... shame on them either way.
Which brings me to another point: if you're not actually trying to use CSS to modify the content of an element, don't use content, even if the fact that it's not defined for use with elements is the reason you're making use of this technique in the first place. You can try assigning those colors to certain classes, creating a hidden element and querying that element's color styles instead.
BoltClock answer shows the cause of my problems. I found a work-around by using the font-family instead of the content CSS property.
My LESS code:
div#colorChart-maincolors {
font-family: '#{colorChart1},#{colorChart2},#{colorChart3},#{colorChart4},#{colorChart5},#{colorChart6}';
}
Which compiled into CSS gives:
div#colorChart-maincolors {
font-family: '#c0392b,#2980b9,#2ecc71,#f1c40f,#ecf0f1,#34495e';
}
The string can be acquired using:
removeQuotes= function(string) {
return string.replace(/^['"]+|\s+|\\|(;\s?})+|['"]$/g, '');
};
removeQuotes($("#colorChart-maincolors").css("font-family")); //add a .split(',') to get the colors as an array
The function removeQuotes is necessary because each browser adds a different kind of quotes into the return of getComputedStyle (and by extension the jQuery .css() method). IE9 adds a double quote, Webkit adds a single quote.
See this post on CSS tricks: http://css-tricks.com/making-sass-talk-to-javascript-with-json/ for more information.
you can use replace(/["']/g, "") to remove extra quotation from string
""string"" will be change to "string"

Get css value without DOM element

I am wondering if there is a way to get css value from stylesheet file when there is no element in the DOM using it? I am using jQuery and I use selector $(".classname").css() to get the values. But with the case "classname" is not in any element, what to do to get the value" Thanks
Just create an element with that class name and inspect it. You don't even need to attach it to the DOM:
var $el = $('<div class="classname"></div>');
var opacity = $el.css('opacity') // or whatever
Even though $el is not actually present in the DOM, you still get access to all of its style properties.
Edit: as mentioned in the comments, this approach does not always work as expected (eg inherited css values not defined on .classname explicitly, selector specificity above .classname, etc).
For example the following fails due to #foo increasing the selector specificity beyond that of a standalone .bar:
css:
#foo .bar { color: red; }
js:
var $el = $('<div class="bar"></div>');
$el.css('color'); // Expected: "red", Actual: ""
See http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript which presents a cross-browser approach to getting and adding css dynamically.
It works with document.styleSheets and both IE's .rules and everyone else's .cssRules
It also has the advantage of being somewhat abstracted so you don't need to worry about the details.
The above link no longer works. The following is a screenshot of the blog-article, captured by the internet archive wayback in 2008.
The function basically iterates over all styles of all stylesheets and provides the ability to modify / delete them.
Please note that this cannot be recommended, since most modern stylesheets are too large to make this an efficient operation.
You could go (this also works in chrome):
var $elem = $('<div id="foo"></div>').appendTo('body'),
value = $elem.css('margin-top');
$('#foo').remove();
You have to parse the styles from document.styleSheets.
To workaround the Chromium problem, you need to add it to the body:
Sample CSS:
.dummy {
min-width: 50px;
max-width: 250px;
}
Sample Javascript:
$(document).ready(function() {
var dummy = $('<div class="dummy" style="display: none;"></div>').appendTo('body');
console.log(dummy.css("min-width"));
console.log(dummy.css("max-width"));
dummy.remove();
});
This works on all modern browsers, Chrome, FF, IE11.

Categories