I'm using parallax.js (Github: https://github.com/pixelcog/parallax.js/) to achieve a parallax effect when scrolling. It works fine on laptop screens. However, when I decrease the horizontal screen size the parallax images don't scale down responsively the moment the horizontal screen size is smaller than the width of the image. For comparison you can have a look at the attached images.
In another parallax library I could achieve responsiveness by using the object-fit
property in its stylesheet.
However, Parallax.js uses js to create a fixed-position element for each parallax image at the start of the document’s body. This mirror element will sit behind the other elements and match the position and dimensions of its target object.
My guesstimation is that I have to tinker around in the js code. Maybe, by using background-size: contain
But as I'm a js newby, I find it quite impossible to implement this.
Can anyone give me some pointers on how I can achieve the images to scale down responsively?
/*!
* parallax.js v1.5.0 (http://pixelcog.github.io/parallax.js/)
* @copyright 2016 PixelCog, Inc.
* @license MIT (https://github.com/pixelcog/parallax.js/blob/master/LICENSE)
*/
;(function ( $, window, document, undefined ) {
// Polyfill for requestAnimationFrame
// via: https://gist.github.com/paulirish/1579671
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
// Detect whether passive event listener is supported
var supportsPassive = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function() {
supportsPassive = true;
}
});
window.addEventListener('test', null, opts);
} catch (e) {}
function passiveOn(el, name, listener) {
if (supportsPassive) {
el.addEventListener(name, listener, {
passive: true
});
} else {
el.addEventListener(name, listener);
}
}
function passiveOff(el, name, listener) {
el.removeEventListenr(name, listener);
}
// Parallax Constructor
function Parallax(element, options) {
var self = this;
if (typeof options == 'object') {
delete options.refresh;
delete options.render;
$.extend(this, options);
}
this.$element = $(element);
if (!this.imageSrc && this.$element.is('img')) {
this.imageSrc = this.$element.attr('src');
}
var positions = (this.position + '').toLowerCase().match(/\S+/g) || [];
if (positions.length < 1) {
positions.push('center');
}
if (positions.length == 1) {
positions.push(positions[0]);
}
if (positions[0] == 'top' || positions[0] == 'bottom' || positions[1] == 'left' || positions[1] == 'right') {
positions = [positions[1], positions[0]];
}
if (this.positionX !== undefined) positions[0] = this.positionX.toLowerCase();
if (this.positionY !== undefined) positions[1] = this.positionY.toLowerCase();
self.positionX = positions[0];
self.positionY = positions[1];
if (this.positionX != 'left' && this.positionX != 'right') {
if (isNaN(parseInt(this.positionX))) {
this.positionX = 'center';
} else {
this.positionX = parseInt(this.positionX);
}
}
if (this.positionY != 'top' && this.positionY != 'bottom') {
if (isNaN(parseInt(this.positionY))) {
this.positionY = 'center';
} else {
this.positionY = parseInt(this.positionY);
}
}
this.position =
this.positionX + (isNaN(this.positionX)? '' : 'px') + ' ' +
this.positionY + (isNaN(this.positionY)? '' : 'px');
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
if (this.iosDisabled) {
if (this.imageSrc && this.iosFix && !this.$element.is('img')) {
this.$element.css({
backgroundImage: 'url("' + this.imageSrc + '")',
backgroundSize: 'cover',
backgroundPosition: this.position
});
}
return this;
}
}
if (navigator.userAgent.match(/(Android)/)) {
if (this.androidDisabled) {
if (this.imageSrc && this.androidFix && !this.$element.is('img')) {
this.$element.css({
backgroundImage: 'url("' + this.imageSrc + '")',
backgroundSize: 'cover',
backgroundPosition: this.position
});
}
return this;
}
}
this.$mirror = $('<div />').prependTo(this.mirrorContainer);
var slider = this.$element.find('>.parallax-slider');
var sliderExisted = false;
if (slider.length == 0)
this.$slider = $('<img />').prependTo(this.$mirror);
else {
this.$slider = slider.prependTo(this.$mirror)
sliderExisted = true;
}
this.$mirror.addClass('parallax-mirror').css({
visibility: 'hidden',
zIndex: this.zIndex,
position: 'fixed',
top: 0,
left: 0,
overflow: 'hidden'
});
this.$slider.addClass('parallax-slider').one('load', function() {
if (!self.naturalHeight || !self.naturalWidth) {
self.naturalHeight = this.naturalHeight || this.height || 1;
self.naturalWidth = this.naturalWidth || this.width || 1;
}
self.aspectRatio = self.naturalWidth / self.naturalHeight;
Parallax.isSetup || Parallax.setup();
Parallax.sliders.push(self);
Parallax.isFresh = false;
Parallax.requestRender();
});
if (!sliderExisted)
this.$slider[0].src = this.imageSrc;
if (this.naturalHeight && this.naturalWidth || this.$slider[0].complete || slider.length > 0) {
this.$slider.trigger('load');
}
}
// Parallax Instance Methods
$.extend(Parallax.prototype, {
speed: 0.2,
bleed: 0,
zIndex: -100,
iosFix: true,
iosDisabled: true,
androidFix: true,
androidDisabled: true,
position: 'center',
overScrollFix: false,
mirrorContainer: 'body',
refresh: function() {
this.boxWidth = this.$element.outerWidth();
this.boxHeight = this.$element.outerHeight() + this.bleed * 2;
this.boxOffsetTop = this.$element.offset().top - this.bleed;
this.boxOffsetLeft = this.$element.offset().left;
this.boxOffsetBottom = this.boxOffsetTop + this.boxHeight;
var winHeight = Parallax.winHeight;
var docHeight = Parallax.docHeight;
var maxOffset = Math.min(this.boxOffsetTop, docHeight - winHeight);
var minOffset = Math.max(this.boxOffsetTop + this.boxHeight - winHeight, 0);
var imageHeightMin = this.boxHeight + (maxOffset - minOffset) * (1 - this.speed) | 0;
var imageOffsetMin = (this.boxOffsetTop - maxOffset) * (1 - this.speed) | 0;
var margin;
if (imageHeightMin * this.aspectRatio >= this.boxWidth) {
this.imageWidth = imageHeightMin * this.aspectRatio | 0;
this.imageHeight = imageHeightMin;
this.offsetBaseTop = imageOffsetMin;
margin = this.imageWidth - this.boxWidth;
if (this.positionX == 'left') {
this.offsetLeft = 0;
} else if (this.positionX == 'right') {
this.offsetLeft = - margin;
} else if (!isNaN(this.positionX)) {
this.offsetLeft = Math.max(this.positionX, - margin);
} else {
this.offsetLeft = - margin / 2 | 0;
}
} else {
this.imageWidth = this.boxWidth;
this.imageHeight = this.boxWidth / this.aspectRatio | 0;
this.offsetLeft = 0;
margin = this.imageHeight - imageHeightMin;
if (this.positionY == 'top') {
this.offsetBaseTop = imageOffsetMin;
} else if (this.positionY == 'bottom') {
this.offsetBaseTop = imageOffsetMin - margin;
} else if (!isNaN(this.positionY)) {
this.offsetBaseTop = imageOffsetMin + Math.max(this.positionY, - margin);
} else {
this.offsetBaseTop = imageOffsetMin - margin / 2 | 0;
}
}
},
render: function() {
var scrollTop = Parallax.scrollTop;
var scrollLeft = Parallax.scrollLeft;
var overScroll = this.overScrollFix ? Parallax.overScroll : 0;
var scrollBottom = scrollTop + Parallax.winHeight;
if (this.boxOffsetBottom > scrollTop && this.boxOffsetTop <= scrollBottom) {
this.visibility = 'visible';
this.mirrorTop = this.boxOffsetTop - scrollTop;
this.mirrorLeft = this.boxOffsetLeft - scrollLeft;
this.offsetTop = this.offsetBaseTop - this.mirrorTop * (1 - this.speed);
} else {
this.visibility = 'hidden';
}
this.$mirror.css({
transform: 'translate3d('+this.mirrorLeft+'px, '+(this.mirrorTop - overScroll)+'px, 0px)',
visibility: this.visibility,
height: this.boxHeight,
width: this.boxWidth
});
this.$slider.css({
transform: 'translate3d('+this.offsetLeft+'px, '+this.offsetTop+'px, 0px)',
position: 'absolute',
height: this.imageHeight,
width: this.imageWidth,
maxWidth: 'none'
});
}
});
// Parallax Static Methods
$.extend(Parallax, {
scrollTop: 0,
scrollLeft: 0,
winHeight: 0,
winWidth: 0,
docHeight: 1 << 30,
docWidth: 1 << 30,
sliders: [],
isReady: false,
isFresh: false,
isBusy: false,
setup: function() {
if (this.isReady) return;
var self = this;
var $doc = $(document), $win = $(window);
var loadDimensions = function() {
Parallax.winHeight = $win.height();
Parallax.winWidth = $win.width();
Parallax.docHeight = $doc.height();
Parallax.docWidth = $doc.width();
};
var loadScrollPosition = function() {
var winScrollTop = $win.scrollTop();
var scrollTopMax = Parallax.docHeight - Parallax.winHeight;
var scrollLeftMax = Parallax.docWidth - Parallax.winWidth;
Parallax.scrollTop = Math.max(0, Math.min(scrollTopMax, winScrollTop));
Parallax.scrollLeft = Math.max(0, Math.min(scrollLeftMax, $win.scrollLeft()));
Parallax.overScroll = Math.max(winScrollTop - scrollTopMax, Math.min(winScrollTop, 0));
};
var scrollListener = this.scrollListener = function() {
loadScrollPosition();
Parallax.requestRender();
};
$win.on('resize.px.parallax load.px.parallax', function() {
loadDimensions();
self.refresh();
Parallax.isFresh = false;
Parallax.requestRender();
})
.on('scroll.px.parallax load.px.parallax', scrollListener);
passiveOn(window, 'touchmove', scrollListener);
loadDimensions();
loadScrollPosition();
this.isReady = true;
var lastPosition = -1;
function frameLoop() {
if (lastPosition == window.pageYOffset) { // Avoid overcalculations
window.requestAnimationFrame(frameLoop);
return false;
} else lastPosition = window.pageYOffset;
self.render();
window.requestAnimationFrame(frameLoop);
}
frameLoop();
},
configure: function(options) {
if (typeof options == 'object') {
delete options.refresh;
delete options.render;
$.extend(this.prototype, options);
}
},
refresh: function() {
$.each(this.sliders, function(){ this.refresh(); });
this.isFresh = true;
},
render: function() {
this.isFresh || this.refresh();
$.each(this.sliders, function(){ this.render(); });
},
requestRender: function() {
var self = this;
self.render();
self.isBusy = false;
},
destroy: function(el){
var i,
parallaxElement = $(el).data('px.parallax');
if (!!parallaxElement.$mirror) {
parallaxElement.$mirror.remove();
}
for(i=0; i < this.sliders.length; i+=1){
if(this.sliders[i] == parallaxElement){
this.sliders.splice(i, 1);
}
}
$(el).data('px.parallax', false);
if(this.sliders.length === 0){
$(window).off('scroll.px.parallax resize.px.parallax load.px.parallax');
passiveOff(window, 'touchmove', this.scrollListener);
this.isReady = false;
Parallax.isSetup = false;
}
}
});
// Parallax Plugin Definition
function Plugin(option) {
return this.each(function () {
var $this = $(this);
var options = typeof option == 'object' && option;
if (this == window || this == document || $this.is('body')) {
Parallax.configure(options);
}
else if (!$this.data('px.parallax')) {
options = $.extend({}, $this.data(), options);
$this.data('px.parallax', new Parallax(this, options));
}
else if (typeof option == 'object')
{
$.extend($this.data('px.parallax'), options);
}
if (typeof option == 'string') {
if(option == 'destroy'){
Parallax.destroy(this);
}else{
Parallax[option]();
}
}
});
}
var old = $.fn.parallax;
$.fn.parallax = Plugin;
$.fn.parallax.Constructor = Parallax;
// Parallax No Conflict
$.fn.parallax.noConflict = function () {
$.fn.parallax = old;
return this;
};
// Parallax Data-API
$( function () {
$('[data-parallax="scroll"]').parallax();
});
}(jQuery, window, document));
.parallax-js-wrapper {
min-height: 400px;
background: transparent
}
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script>
<div class="container-fluid">
<p class="mb-5">Lorem ipsum dolor sit amet. Hic nobis ipsum aut fugiat molestiae est impedit officia At perferendis quisquam est illo molestiae cum minus corrupti. Ab numquam corporis qui corporis temporibus in numquam deleniti. Ut maxime expedita quo facilis unde aut earum eaque non sunt porro ut ipsa blanditiis. </p>
<div class="parallax-js-wrapper" data-natural-width="1260"
data-natural-height="750" data-parallax="scroll" data-bleed="25" data-speed="-0.2" data-image-src="https://images.pexels.com/photos/933054/pexels-photo-933054.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
alt="" title=""></div>
<p class="my-5">Lorem ipsum dolor sit amet. Hic nobis ipsum aut fugiat molestiae est impedit officia At perferendis quisquam est illo molestiae cum minus corrupti. Ab numquam corporis qui corporis temporibus in numquam deleniti. Ut maxime expedita quo facilis unde aut earum eaque non sunt porro ut ipsa blanditiis. </p>
<div class="parallax-js-wrapper" data-natural-width="1260"
data-natural-height="750" data-parallax="scroll" data-bleed="25" data-speed="0.3" data-image-src="https://images.pexels.com/photos/933054/pexels-photo-933054.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
alt="" title=""></div>
<p>Lorem ipsum dolor sit amet. Hic nobis ipsum aut fugiat molestiae est impedit officia At perferendis quisquam est illo molestiae cum minus corrupti. Ab numquam corporis qui corporis temporibus in numquam deleniti. Ut maxime expedita quo facilis unde aut earum eaque non sunt porro ut ipsa blanditiis. </p>
</div>