Question:
There is a parallax effect on the Apple website ("The Most Powerful iMac Ever"). When scrolling to a certain point, the picture starts scrolling slower than the text, and at the end the slow scrolling is replaced by the standard one. How can the same effect be achieved?
UPD:
There is a script that, when scrolling to a certain height, slows down the scrolling of the block. First, we have a block with absolute positioning, when scrolling a certain number of pixels, the block gets a fixed position and starts scrolling slower than the main content.
// Наш элемент, который будет замедляться
var $element = $('.first'),
/*
* Последняя позиция скролла, нужна
* для вычисления
*/
lastScrollTop = 0,
/*
* Отступ сверху для нашего блока
* с фиксированным и абсолютным
* позиционированием
*/
defaultElementAbsoluteTop = 150,
defaultElementFixedTop = 90,
// Коэффициэнт замедления
n = 4;
// Отслеживаем скролл страницы
$(window).scroll(function (event) {
// Вычисляем текущее положение
var scrollTop = $(window).scrollTop(),
/*
* Вычисляем положение нашего блока
* от верхней части сайта/родителя.
* Т.к. этот элемент до этого был абсолютным,
* то у него отступ сверху всегда одинаков,
* А когда подходит момент, когда нужно его фиксировать
* отступ расчитывается от последнего, который был
* 150 пикселей. Это неправильно. Я ввел проверку,
* Если отступ стандартный абсолютный равен
* текущему, то, при фиксировании блока
* ставим ему оступ фиксированного блока
* и отсюда начинаются правильные расчеты
*/
elementTop = $element.position().top == defaultElementAbsoluteTop ? defaultElementFixedTop : $element.position().top;
if(scrollTop < 60)
{
/*
* Проскролили меньше, чем на 60 пикселей
* То оставляем абсолютное позиционирование
*/
$element.removeClass('fixed');
// И ставим элементу абсолютный отступ сверху
$element.css({
top: defaultElementAbsoluteTop
});
}
else
{
// Фиксируем элемент
$element.addClass('fixed');
// TODO: удалить!
//return;
/*
* Если прошлое положение меньше,
* чем текущее, значит двигаемся <вниз>
*/
if (scrollTop > lastScrollTop){
// Вычисляем разницу перемещения
var diff = scrollTop - lastScrollTop;
// Даем отступ сверху в n раз меньше, чем скролл
$element.css({
top: elementTop - diff/n + 'px'
});
// В противном случае, двигаемся <вверх>
} else {
// Вычисляем разницу перемещения
var diff = lastScrollTop - scrollTop;
/*
* Вычисляем результирующее положение нашего элемента,
* с учетом замедления n
*/
resultElementTop = elementTop + diff/n;
/*
* Если конечный результат вышел больше,
* чем отступ сверху по умолчанию,
* то даем стандартный отступ.
* Иначе даем результирующий отступ
*/
resultElementTop = resultElementTop > defaultElementFixedTop ? defaultElementFixedTop : resultElementTop;
// Позиционируем элемент
$element.css({
top: resultElementTop + 'px'
});
}
}
// Запоминаем последнюю позицию скролла
lastScrollTop = scrollTop;
});
It is necessary that the block first be with position: absolute, when scrolling to a certain area, the block gets position: fixed and scrolls more slowly. When scrolling down the page even further down, the block becomes absolutely spliced again. When scrolling back up, the block gets a fixed position and scrolls down from where it left off when scrolling up.
Answer:
My solution is an example here . A description of everything that happens, in principle, is in the code below. Partially used the idea from the Apple page, but did without translate3D
.
Basic HTML:
<div class="content"><!-- контент --></div>
<div class="content">
<div class="slow"></div>
<!-- контент -->
</div>
<div class="content"><!-- контент --></div>
Main CSS:
.content {
position: relative;
}
.slow {
position: absolute;
top: 100px;
}
JS:
var $window = $(window);
var $parent = $('.content').eq(1);
var $slow = $('.slow');
// сохраняем первоначальную позицию блока по отношению к родителю (100px)
var initialTop = $slow.position().top;
// эффект замедления
// то, на сколько пикселей будет двигаться блок при position: fixed
var translate = 1;
// указываем расстояние в пикселях до/после родителя, чтобы знать
// когда добавлять/убирать position: fixed
// могут быть равны 0, тогда определяться будет по позиции родителя
var parentBefore = 300,
parentAfter = 100;
// последнее значение $window.scrollTop(), для определения направления скролла
var previousScrollTop = 0;
// направление по умолчанию - вниз
var direction = -1;
// временные переменные
var parentTop = null,
parentBot = null;
function onScroll() {
// узнаем в каком направлении мы сейчас скроллим
// "1" - вверх, "-1" - вниз
direction = previousScrollTop > $window.scrollTop() ? 1 : -1;
previousScrollTop = $window.scrollTop();
// вычисляем верхнюю и нижнюю границу того, когда нам нужно начинать эффект
// верхняя граница: когда топ родителя + запас "before" достигает топа окна
// нижняя граница: когда низ родителя + запас "after" появляется снизу
var borderTop = $parent.offset().top - parentBefore - $window.scrollTop();
var borderBot = borderTop + parentAfter + $parent.height();
// если мы внутри границ
if (borderBot > $window.height() && borderTop <= 0) {
// достаем текущее положение элемента относительно окна
var currentTop;
// если скролим вверх, то при первом переключении на position: fixed,
// восстанавливаем сохраненное значение (см. parentTop)
if ($slow.css('position') === 'absolute') {
currentTop = parentTop;
} else {
// иначе берем текущую фиксированную позицию
currentTop = parseInt($slow.css('top'), 10);
}
// вычисляем новое значение со сдвигом в зависимости от направления
var nextTop = currentTop + translate * direction;
// проверка для того, чтобы не вылезать за пределы стартовой позиции
if (nextTop < initialTop) {
nextTop = initialTop;
}
// когда впервые попадаем в границы, добавляем запас "before" к позиции
if (!parentTop) {
nextTop += parentBefore;
}
$slow.css('position', 'fixed');
$slow.css('top', nextTop);
$slow.css('bottom', 'auto');
// сохраняем позицию относительно окна сверху
parentTop = $slow.position().top;
// сохраняем позицию относительно окна снизу
parentBot = $window.height() - $slow.position().top - $slow.height();
// если вышли за границу снизу, цепляем блок к низу родителя
} else if (borderBot < $window.height()) {
$slow.css('position', 'absolute');
$slow.css('top', 'auto');
// устанавливаем позицию блока относительно родителя из сохраненного
// значения (см. parentBot), с вычетом запасов "before" и "after"
$slow.css('bottom', parentBot - parentAfter + parentBefore + 'px');
// если вышли за границу сверху, восстанавливаем все состояния
} else if (borderTop > 0) {
$slow.css('position', 'absolute');
$slow.css('top', initialTop);
$slow.css('bottom', 'auto');
parentTop = null;
parentBot = null;
}
}
$window.on('scroll', onScroll);
Noticed disadvantages:
- you cannot slow down the movement of a fixed block by less than 1px (
translate
variable) - the position of the block when scrolling is always random, because the move is performed on every
scroll
event.
Edit: JSBin in Firefox didn't see the onScroll
function that was declared under $window.on('scroll', onScroll);
. Also, Firefox, even if the element has top:auto
, returns a numeric value for calling $el.css('top')
, not the string auto
.