How to change value of CSS variable as page scrolls gsap? - javascript

I have a css variable named --color. I want to change the value of this css variable as the page scrolls down. The scrolling of the page should "scrub" the value.
Example: --color starts out at rgb(255, 255, 255). As the page scrolls down, the value gets closer to and eventually reaches rgb(0, 0, 0) upon scrolling down all the way. When scrolling back up, the value should slowly advance back to rgb(255, 255, 255), reaching it by the time the page is scrolled up completely.
I have been trying to achieve this with GSAP but have yet to find any success, although I believe it must be possible with the library.

Sure, here you go:
gsap.to("body", {
"--color": "black",
scrollTrigger: {
start: 0,
end: "max",
scrub: true
}
});
https://codepen.io/GreenSock/pen/WNOzWaM?editors=0110
By the way, there are dedicated GSAP forums at https://greensock.com/forums

unfortunately I am not familiar with GSAP, but
here is the link that describes how to get the computed styles and the property value of that style with JS.
You need to listen to scroll event and change the value of the css variable inside of the listener, like so:
document.addEventListener('scroll', function(e) {
var root = document.querySelector(':root');
var newColor = // do your color calculations here;
root.style.setProperty('--color', newColor);
}
here are some useful links.
https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/setProperty
https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/getPropertyValue
https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle

Here is an average calculation for your rgb() color that might be fine enough to go from light to dark and back. It is far to be perfect.
const body = document.querySelector("body ");
const bodyHeight = document.querySelector("body").offsetHeight;
body.innerHTML = '<p style="position:sticky;top:0;">Body Height: ' + bodyHeight + 'px <br>Window Height: ' + window.innerHeight + 'px</p>';
const root = document.querySelector(":root");
window.addEventListener("scroll", (event) => {
let scroll = this.scrollY;
let ratio = (scroll / (bodyHeight - window.innerHeight ) ) ;
let rgbvalue = 255 - ratio * 255;
root.style.setProperty( "--color", "rgba(" + rgbvalue + "," + rgbvalue + "," + rgbvalue + ")"
);
console.log(rgbvalue);
});
body {
background: var(--color, rgb(255, 255, 255));/* use defaul trgb(255, 255, 255) untill var is set from js */
height: 300vh;
}
<div></div>

It's helpful when you provide a code snippet to show what has already been attempted. Helps with specificity of the answer.
To give a general answer to your question, you can use ScrollTrigger to define the point at which the color changes. Define the markers where it should start changing and use properties such as coloronEnter, onLeave, onEnterBack to your callback to change the color.

Related

Create a wiggle effect for a text

So what I want to happen is that when viewing the Span the text is normal but as you scroll down it starts moving until it looks like such:
Before the effect:
While the effect occurs:
The header is represented by spans for each letter. In the initial state, the top pixel value for each is 0. But the idea as mentioned is that that changes alongside the scroll value.
I wanted to keep track of the scroll position through JS and jQuery and then change the pixel value as needed. But that's what I have been having trouble with. Also making it smooth has been another issue.
Use the mathematical functions sine and cosine, for characters at even and odd indices respectively, as the graphs of the functions move up and down like waves. This will create a smooth effect:
cos(x) == 1 - sin(x), so in a sense, each character will be the "opposite" of the next one to create that scattered look:
function makeContainerWiggleOnScroll(container, speed = 0.01, distance = 4) {
let wiggle = function() {
// y-axis scroll value
var y = window.pageYOffset || document.body.scrollTop;
// make div pseudo-(position:fixed), because setting the position to fixed makes the letters overlap
container.style.marginTop = y + 'px';
for (var i = 0; i < container.children.length; i++) {
var span = container.children[i];
// margin-top = { amplitude of the sine/cosine function (to make it always positive) } + { the sine/cosine function (to make it move up and down }
// cos(x) = 1 - sin(x)
var trigFunc = i % 2 ? Math.cos : Math.sin;
span.style.marginTop = distance + distance * trigFunc(speed * y)/2 + 'px';
}
};
window.addEventListener('scroll', wiggle);
wiggle(); // init
}
makeContainerWiggleOnScroll(document.querySelector('h2'));
body {
height: 500px;
margin-top: 0;
}
span {
display: inline-block;
vertical-align: top;
}
<h2>
<span>H</span><span>e</span><span>a</span><span>d</span><span>e</span><span>r</span>
</h2>
Important styling note: the spans' display must be set to inline-block, so that margin-top works.
Something like this will be the core of your JS functionality:
window.addEventListener('scroll', function(e) {
var scrl = window.scrollY
// Changing the position of elements that we want to go up
document.querySelectorAll('.up').forEach(function(el){
el.style.top = - scrl/30 +'px';
});
// Changing the position of elements that we want to go down
document.querySelectorAll('.down').forEach(function(el){
el.style.top = scrl/30 +'px';
});
});
We're basically listening in on the scroll event, checking how much has the user scrolled and then act upon it by offsetting our spans (which i've classed as up & down)
JSBin Example
Something you can improve on yourself would be making sure that the letters wont go off the page when the user scrolls a lot.
You can do this with simple math calculation, taking in consideration the window's total height and using the current scrollY as a multiplier.
- As RokoC has pointed out there is room for performance improvements.Implement some debouncing or other kinds of limiters

Parallax background image for top-bottom and not right-left

I wrote a script that changes the BG position as you scroll down, its works good for left and right positions but i cant seem to reach the syntax that will allow me to parallax the background-position top or bottom - instead of right and left.
here is my code:
function parallax(){
var scrolled = $(window).scrollTop();
$('section.intro .custombg').css('background-position',(scrolled * -0.2) + 'px');}
$(window).scroll(function(e){
parallax();
});
}
The css attribute background-position has two values, #horizontal #vertical.
See: http://www.w3.org/wiki/CSS/Properties/background-position
Consider something like:
function parallax(){
var scrolledTop = $(window).scrollTop();
var scrolledLeft = $(window).scrollLeft();
$('section.intro .custombg').css('background-position',(scrolledLeft * -0.2) + 'px ' + (scrolledTop * -0.2) + 'px');}
$(window).scroll(function(e){
parallax();
});
}
Also, this seems like it will add the scroll event every time the parallax method is called. To correct this, you could try:
function parallax(top, left) {
$('section.intro .custombg').css('background-position',(left * -0.2) + 'px ' + (top * -0.2) + 'px');}
} // end function
$(document).ready(function() {
$(window).scroll(function(e) {
parallax($(window).scrollTop(), $(window).scrollLeft()); // call the method
});
});
This is all wrong, you are setting up an event handler each time you use $(window).scroll(). You only need to do that once. Try this.
var scrolledTop,
scrolledLeft,
background_position,
$custom_bg;
function parallax(){
scrolledTop = window.scrollY,
scrolledLeft = window.scrollX,
background_position = (scrolledLeft * -0.2) + 'px ' + (scrolledTop * -0.2) + 'px');
console.log('background_position', background_position);
$custom_bg.css('background-position', background_position);
}
$(function() {
$custom_bg = $('section.intro .custombg');
$(window).on('scroll', parallax);
});
try
function parallax(){
var scrolled = $(window).scrollTop();
$('section.intro .custombg').css('background-position','center ' + (scrolled * -0.2) + 'px');}
$(window).scroll(function(e){
parallax();
});
}
If you're looking to do more with parallax and change other properties as, I'd highly recommend the Skrollr library (https://github.com/Prinzhorn/skrollr).
You can vary almost any CSS property as you scroll, giving you more options than just background position or something else. It might be more than you're looking for, but it's pretty lightweight and has mobile support, too (which you could have trouble accounting for without a well-developed library). Hope it helps!
For example, if you wanted to shift the background-position of a background image, you could simply do the following:
initialize skrollr (in this case without options, but there are parameters you can set)
<script type="text/javascript">
var s = skrollr.init();
</script>
Then, you're able to use simple data-tags to tell Skrollr which elements you want to make fancy and which you don't. In your case, you could do something vaguely like the following:
(whatever element you want to use parallax on)
<div data-0="background-color:rgb(0,0,255);transform[bounce]:rotate(0deg);" data-500="background-color:rgb(255,0,0);transform[bounce]:rotate(360deg);">
WOOOT
</div>
However, you'd swap background-color out for background-position
<div data-0="background-position: 0px 0px" data-700="background-position: 0px 100px"> </div>
or
<div data-0="background-position: top center" data-700="background-position: bottom center"> </div>
You can use any of the accepted CSS background-position keywords.
Useful:
https://github.com/Prinzhorn/skrollr

animating a rectangle with text in Raphael javascript library

I am quite new to javascript and to Raphael. I am trying to move a button-like rectangle with text inside. Here is my code :
window.onload = function() {
var paper = new Raphael(document.getElementById('canvas_container'), "100%", "100%");
var box1 = paper.rect(100, 100, 120, 50, 10).attr({fill: 'darkorange', stroke: '#3b4449', 'stroke-width': 2, cursor: 'pointer'});
var box2 = paper.rect(400,100,120,50,10).attr({fill: 'lightblue', stroke: '#3b4449', 'stroke-width': 2});
var text2 = paper.text(box2.attrs.x + box2.attrs.width/2,box2.attrs.y + box2.attrs.height/2,"[x: " + box2.attrs.x + " y: " + box2.attrs.y + "]").attr({"font-family": "arial", "font-size": 16});
var button2 = paper.set();
button2.push(box2);
button2.push(text2);
box1.click(function(){
// this did not work
// button2.animate({x: 100, y: 50 }, 1000, 'bounce', function() { // callback function
// text2.attr('text',"[x: " + box2.attrs.x + " y: " + box2.attrs.y + "]");
// });
button2.animate({transform: "t100,100"}, 1000, 'bounce', function() { // callback function
text2.attr('text',"[x: " + box2.attrs.x + " y: " + box2.attrs.y + "]");
});
});
}
The button2.animate({x: 100, y: 50 }, 1000, 'bounce'); line did not worked properly, the text was not in the right position at the end. By using the transform: I can not use coordinates, I would have to compute them. Also I am not able to get the right coordinates of the blue box at the end when using the transform method.
I was not able to find any answer yet, hope someone can help me.
Thank you
Since you didn't explain how exactly you want to move your button, I'm assuming you want to move the box2 above box1.
There are some misunderstandings and errors in your code, allow me explain one by one.
Why the first way cause text move to wrong position at end ?
Because a set is NOT a group of element which knows its relative position inside the group. A set is merely a collection of elements which is designed for us to operate them in a more convenient way.
So, the code below will move all element in the set to (100, 50)
set.animate({x: 100, y: 50 }, 1000);
and that's why the text is there.
I couldn't find the document, but you can find some explanation here .
Why x, y in attributes seems to be wrong when using transform ?
No, the attribute is correct.
When you transform an element, the result of the transformation will not reflect back to the attributes. You can think like this, when transform(), you are actually attach "transformation" to the elements. Therefore :
paper.circle(100, 100, 5).transform("t100");
You can describe the circle as :
a circle at (100, 100) which will be moved 100px horizontally.
but not - a circle at (200, 100) which will be moved 100px horizontally.
So, here is the code that dose what you want, note that I'm using getBBox() to get coordinate of the button2 set.
box1.click(function(){
var moveX = box1.attr("x") - button2.getBBox().x;
var moveY = (box1.attr("y") - 50) - button2.getBBox().y;
button2.animate({transform: "t" + moveX + "," + moveY}, 1000, 'ease-in-out', function () {
text2.attr('text', "[x: " + button2.getBBox().x + "y: " + button2.getBBox().x + "]");
});
});
Welcome to SO, and suggest you to write a SSCCE next time.
UPDATE
I do not fully understand why the transformation does not reflect back
to the attributes. If I move the circle at the position (100,100)
100px horizontally it will results in a circle at position (200,100).
This is what the bounding box gives me. So why I am not able to get
the coordinates from the circle after the transformation and have to
use the bounding-box method ?
Transform DOSE NOT change the original attribute in the element, because it is something you attach to a element, not function that change a element directly. If you want to know attributes AFTER the transformation applied, you have to use getBBox(), or take a look about matrix.
This is how Raphael.js works. Either you use bounding box function, or extend the Raphael.js by yourself like this
I have changed my previous answer about how I describe transformation a little bit, hope it can help you to understand better this time.
Your code works great but it has the drawback, that you have to
compute the transformation values instead of simply setting the
position. Is there any other way to move a rectangle with text inside
to a position of your choice ?
You can always write helper functions to do these ugly jobs for you anyway, I don't see there's anything wrong with it.

Changing -webkit-mask-position through javascript

I'm working on an iPad webview project that requires a slider to move back and forth over two pictures, revealing one or the other when sliding to the left or right. Due to project restraints, I can't utilize jQuery. I currently have the left picture on top with a transparent mask over it and when the -webkit-mask-position is increased, it reveals more of the bottom picture, when decreased, more of the top (covering the bottom one).
I'm using a javascript plugin called Draggy (https://github.com/jofan/Draggy) to move the slider back and forth and want to use its onChange function call to update the position of the mask, but I can't figure out what javascript calls "-webkit-mask-position" to save my life.
Any ideas?
PS: webkitMaskPosition adds style="-webkit-mask: XX" to the element, which I COULD use (filling in the other values in the js), but it's being really buggy. I'm looking into it now.
OH MAN I GOT IT.
var maskSlider = document.getElementById('molecule');
function moveMask(x, y) {
var xx = x - 285;
var z = "-webkit-gradient(linear, left center, right center, color-stop(0.5, black), color-stop(0.5, transparent)) no-repeat scroll " + xx + "px padding padding";
maskSlider.style.webkitMask = z;
}
The -285 is to get it to line up to where the slider is on the image. I have no idea if I'm really doing this the right way, but it worked. If anyone can think of a better/ more efficient way to do this, please let me know.
I've recently worked a lot about webkit masks which are using CSS3 gradients to make transitions between images.
I preferred to append a style tag to head tag which is made dynamically by my calculations. And also I used CSS3 animation too. Something like this:
var cssStr = '<style id="myTransition">.myTransition_wipe{' +
'-webkit-mask-size: 200% 200%;' +
'-webkit-mask-repeat: no-repeat;' +
'-webkit-animation: wipe 2s;' +
'-webkit-animation-direction: normal;' +
'-webkit-animation-iteration-count: 1;' +
'-webkit-animation-fill-mode: forwards;' +
'-webkit-mask-position: offsetLeftS 0;' +
'-webkit-mask-image: -webkit-gradient(linear, left top, right top, ' +
'color-stop(0%, transparent), color-stop(20%, transparent), ' +
'color-stop(25%, transparent), color-stop(30%, transparent), ' +
'color-stop(50%, rgba(0, 0, 0, 1)), color-stop(98%, rgba(0, 0, 0, 1)), ' +
'color-stop(100%, rgba(0, 0, 0, 1)));}' +
'#-webkit-keyframes wipe {' +
'0% { -webkit-mask-position: offsetLeftS 0; }' +
'100% { -webkit-mask-position: offsetLeftD 0; }' +
'}' +
'</style>';
var compStyle = getComputedStyle(currentElement);
var width = parseInt(compStyle.getPropertyValue("width"));
var height = parseInt(compStyle.getPropertyValue("height"));
cssStr = cssStr.replace(/offsetLeftS/g, '-' + (width * 1) + 'px');
cssStr = cssStr.replace(/offsetLeftD/g, '+' + (width * 1) + 'px');
$('head').append(cssStr);
When I want to apply a transition, I remove the style tag by its ID and remove the class myTransition_wipe from element too, then append a new one to head tag and add the class name to element too.
Pay Attention: You must store the styles that element should have after the animation is finished and add them right after the animation is finished. Otherwise, when you remove class name and style tag, everything will be reset.
Good Luck

(jQuery plugin: backstretch) Margin on the sides

I am using this great jQuery plugin to have the fullscreen backgound for my website.
This plugin currently fills the entire background on the screen, I was wondering if it is possible to give it a margin.
For instance I want to have a gap in the right side of the screen for 150px (so I can see the body background) and the rest of the page will be filled with backstretch.
I have played with _adjustBG function but I can't get this working.
Any helps will be appreciated.
Since the author of this plugin didn't make an option for margin, I'll tweak it for you.
Below is the modified _adjustBG() function that you may need.
Just open the file "jquery.backstretch.js" (the normal version, not the minimized) then replace the original _adjustBG() function (at the end of file) with this function.
function _adjustBG(fn) {
var rightMargin = 150; //--- edit the margin value here
try {
bgCSS = {left: 0, top: 0}
bgWidth = rootElement.width()-rightMargin;
bgHeight = bgWidth / imgRatio;
// Make adjustments based on image ratio
// Note: Offset code provided by Peter Baker (http://ptrbkr.com/). Thanks, Peter!
if(bgHeight >= rootElement.height()) {
bgOffset = (bgHeight - rootElement.height()) /2;
if(settings.centeredY) $.extend(bgCSS, {top: "-" + bgOffset + "px"});
} else {
bgHeight = rootElement.height();
bgWidth = bgHeight * imgRatio-rightMargin;
bgOffset = (bgWidth - rootElement.width()) / 2;
if(settings.centeredX) $.extend(bgCSS, {left: "-" + bgOffset + "px"});
}
$("#backstretch, #backstretch img:last").width( bgWidth ).height( bgHeight )
.filter("img").css(bgCSS);
} catch(err) {
// IE7 seems to trigger _adjustBG before the image is loaded.
// This try/catch block is a hack to let it fail gracefully.
}
// Executed the passed in function, if necessary
if (typeof fn == "function") fn();
}
Update:
By poking around w/ console, I found that if you subtract 150 from the width of the background-image, it will, by default, give you a margin on the right. You may want to adjust the height so your image scales, but, maybe something like this to run in $(document).ready():
var $bg = $('#backstretch');
var newImgWidth = $bg.width() - 150;
$bg.css('width', newImgWidth);
If IE6 is no issue, you can try to put the following in your stylesheet:
#backstretch{
width: auto !important;
right: 150px;
}
I tried this on the backstretch homepage and it worked as I would expect. As I am not totally familiar with this plugin please feel free to correct me.

Categories