How to fix flickering animation in css / javascript - javascript

I want to set a scale and a margin of an element to make it centered in a fluid way by using wheel event.
I want to use margins for centering as at some point I would like to set scroll position on wrapping element in fluid way as well.
As you can see in jsfiddle example I'm using css transition attribute to make it fluid.
At the same time I'm changing a scale and a margin but it looks like margin animation kicks in faster leading to moving the div side ways first. You can try it with greater zoom and do mousewheel up and down.
How to fix it so it starts and ends simultanously so the cross located in the middle of the picture doesn't move sideways during zooming in and out?
jsfiddle
<html>
<div id="wrap">
<div id="el">
+
</div>
</div>
</html>
var scale = 1;
var $wrap = $('#wrap');
var $el = $('#el');
$(function() {
$el.on('wheel', function (e) {
scale = e.originalEvent.wheelDelta > 0 ? scale * 1.5: scale / 1.5;
e.preventDefault();
var l = ($wrap.width() - $el.width() * scale) / 2;
var t = ($wrap.height() - $el.height() * scale) / 2;
$el.css({
'transform': "scale(" + scale + ")",
'margin-top': t + "px",
'margin-left': l + "px",
});
});
});
#wrap {
position: fixed;
width: 400px;
height: 300px;
background: pink;
}
#el {
position: relative;
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
transition: 1s;
transform-origin: 0 0;
background-image: radial-gradient(circle at 0px 0px, #666 1px, transparent 0);
background-size: 4px 4px;
}
Edit: The best way to see the issue is to use mouse scroll once (one tick) and at the end of animation scroll it once again.
Edit2: So I've used Gabriele Petrioli answer and it looks good, however I still needs this info about position of the element. I came up with solution where I 'move'd margins to css transform attribute as 'translate' option and it looks working ok
I've replaced:
$el.css({
'transform': "scale(" + scale + ")",
'margin-top': t + "px",
'margin-left': l + "px",
});
with:
$el.css({
'transform': "translate(" + t + "px, " + l + "px) scale(" + scale + ")",
});
jsfiddle

I would use absolute positioning and position it at the center from the start.
Some changes
use position:absolute and left/right to position it in the center
use transform: translate(-50%, -50%) to match the grid center with the wrapper center
set the origin to 50% 50% as well so you do not have to account for movement
now that you do not need the margins, you can just adjust the scale
added a Math.max/Math.min in there just to keep the example sane for testing.
var scale = 1;
var $wrap = $('#wrap');
var $el = $('#el');
$(function() {
$el.on('wheel', function(e) {
e.preventDefault();
scale = e.originalEvent.wheelDelta > 0 ? scale * 1.5 : scale / 1.5;
scale = Math.min(Math.max(scale, 0.2) ,20);
$el.css({
'transform': `translate(-50%,-50%) scale(${Math.max(scale,0.1)})`,
});
});
});
#wrap {
position: fixed;
width: 400px;
height: 300px;
background: pink;
}
#el {
position: absolute;
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
transition: transform 1s;
transform-origin: 50% 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-image: radial-gradient(circle at 0px 0px, #666 1px, transparent 0);
background-size: 4px 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<div id="wrap">
<div id="el">
+
</div>
</div>
</html>

Related

Problems handling the scrolling of the webpage with javascript moving an element

I need a little help: here is the piece of code incriminated:
html:
<div class="contain">
<div class="background-image background-cover"></div>
<div class="circle">
<div class="inside blur-big background-cover"></div>
<div class="inside blur-small background-cover"></div>
<div class="inside clear background-cover"></div>
</div>
</div>
CSS:
.contain {
position: relative;
height: 200px;
margin-left: -98px;
width: 150%;
}
.background-cover {
width: 100%;
height: 100%;
background: no-repeat center center fixed;
background-size: cover;
-webkit-background-size: cover;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgb(36, 36, 36);
background-attachment: scroll;
background-image: url(https://dummyimage.com/600x400/000/fff);
}
.circle {
position: absolute;
border-radius: 100%;
width: 115px;
height: 115px;
box-sizing: border-box;
}
.circle .inside {
position: absolute;
top: 0;
left: 0;
border-radius: 100%;
background-image: url(https://dummyimage.com/600x400/000/fff);
}
.blur-big {
height: 100%;
width: 100%;
-webkit-filter: blur(3px);
filter: blur(3px);
}
/* positioned slightly 'inside' the big blur */
.blur-small {
margin: 5px;
height: 105px;
width: 105px;
-webkit-filter: blur(1px);
filter: blur(1px);
}
.clear {
margin: 10px;
height: 95px;
width: 95px;
}
JavaScript:
if ('ontouchstart' in window) {
} else
addContainMouseMoveFunctionality(); //just add this functionality when it'll be used
function addContainMouseMoveFunctionality() {
//do as little as possible in the mousemove-event, so initiate variables here
let circle = document.getElementsByClassName('circle')[0];
let contain = document.getElementsByClassName('contain')[0];
//add event listener for the mouse enters the image, show circle
contain.addEventListener("mousemove", moveCircle);
//add event listener for when the mouse leaves the image, hide circle
contain.addEventListener("mouseout", function(e) {
//give the circle a position outside the viewport
circle.style.top = '-999px';
circle.style.left = '-999px';
});
function moveCircle(e) {
//get the offset from the top to avoid the circle going all over the place when scrolling down or horizontally
let doc = document.documentElement;
let left = window.pageXOffset - doc.scrollLeft + doc.clientLeft;
let top = window.pageYOffset - doc.scrollTop + doc.clientTop;
//give the circle a position near the mouse, position minus half of its width/height to center it
circle.style.top = top + e.pageY - circle.offsetHeight / 2 + 'px';
circle.style.left = left + e.pageX - circle.offsetWidth / 2 + 98 + 'px'; // 98 is for the margin of contain
}
}
I think the problem is with these formulas but maybe not:
circle.style.top = top + e.clientY - circle.offsetHeight / 2 + 'px';
circle.style.left = left + e.clientX - circle.offsetWidth / 2 + 98 + 'px';
Here is a fiddle which shows how it works: https://jsfiddle.net/hw615quf/7/https://jsfiddle.net/hw615quf/7/
When you scroll a little to the right, you will see what the problem is: the circle which was centred around the cursor is now no more centred...
It is not that problematic in this fiddle but when I incorporate my code on the Wordpress site with Salient, the circle is pretty much down and left from the cursor's position. When I scroll down, the circle is a little bit closer to the image but still not centred... And anyway, I don't want my circle to be centred only when the image is barely visible.
Maybe it is my formula which is problematic? Can anyone help me with this scroll?
Thanks for your help and for reading, have a nice day/evening/night :)
Benjamin
clientX and clientY does not change as page scroll changes they remain fixed. You should use pageX and pageY in this scenario. Like this
circle.style.top = top + e.pageY - circle.offsetHeight / 2 + 'px';
circle.style.left = left + e.pageX - circle.offsetWidth / 2 + 98 + 'px';
Here is a working example https://jsfiddle.net/681Lakn0/

Div centering vertically when mobile page is at top, but when we scroll down the page, error is occuring

I am building a cordova app. When I click on div, its conponents are shown and the popup has to be in the center of screen. This is my code.
var new_height = $('#' + id).height() + 20;
var new_width = $('#' + id).width() * 0.97;
$('#' + id + '_popup').width(new_width);
var move_up = $('#' + id + '_popup').height() / 2;
$('#' + id + '_popup').css({
"overflow-y": 'auto',
'transform': 'translateY(-' + $('#' + id + '_popup').height() / 2 + 'px)'
});
$('#' + id + '_popup').css("z-index", '3000');
But when I scroll the page, the new div is not displaying in the center of div but above. What should I do?
You can achieve perfect center only with CSS, so remove that useless calculations and use transform like this:
.red {
position: absolute;
height: 100%;
width: 100%;
background: red;
}
.green {
position: absolute;
height: 40%;
width: 40%;
background: green;
top: 50%;
left:50%;
-webkit-transform: translate(-50%, -50%); /*safari iOS*/
transform: translate(-50%, -50%);
}
<div class="red">
<div class="green">
</div>
</div>
With this code doesn't matter the sizes of the boxes, it will be always at the center of the screen.
You can change position to fixed or relative and still will work.

Getting bounding box of an HTML element excluding CSS transforms

I realise this used to be the default behaviour of getBoundingClientRect() but it seems that I'm in the rare position of needing this feature!
I have a CSS animation that moves a div across the Y axis using translate. However, I want the finishing position of the div ...but before the animation has even begun.
Is there any (neat) way of doing this assuming I have no knowledge of the animation parameters?
You can use getBoundingClientRect just as is.
Here is an example:
var element = document.getElementById("move");
window.setInterval(function() {
var structure = element.getBoundingClientRect();
element.innerHTML = "left: " + Math.floor(structure.left) + "px<br/> top: " + Math.floor(structure.top) + "px";
}, 100);
body {
background-color: #222;
height: 450px;
}
#move {
position: absolute;
width: 200px;
height: 200px;
background-color: white;
animation: move 5s infinite alternate;
}
#keyframes move {
from {
left: 0;
top: 0;
}
to {
left: calc(100% - 200px);
top: calc(100% - 200px);
}
}
<div id="move">HELLO!</div>

How to emulate background-size: cover on <img>?

How can I resize and reposition the image inside a box, in such way that it covers the entire box, similar to how background-size: cover works.
<div class="box" style="width: 100px; height: 100px;">
<img src="pic.jpg" width="413" height="325">
</div>
I know I have to add overflow:hidden to the box and the image needs position: absolute. But what's the formula that gets me the right new size for the image, and left + top positions?
For what it's worth: this can now be done with CSS alone with...
The new CSS property object-fit (Current browser support)
Just set object-fit: cover; on the img
You don't even need to wrap the img in a div!
FIDDLE
img {
width: 100px;
height: 100px;
}
.object-fit {
display: block;
object-fit: cover;
}
.original {
width: auto;
height: auto;
display: block;
}
<img src="http://lorempixel.com/413/325/food" width="413" height="325">
<p>Img 'squashed' - not good</p>
<img class="object-fit" src="http://lorempixel.com/413/325/food" width="413" height="325">
<p>object-fit: cover -
The whole image is scaled down or expanded till it fills the box completely, the aspect ratio is maintained. This normally results in only part of the image being visible. </p>
<img class="original" src="http://lorempixel.com/413/325/food" width="413" height="325">
<p>Original ing</p>
You can read more about this new property in this webplatform article.
Also, here is a fiddle from the above article which demonstrates all the values of the object-fit property.
Close enough, pure CSS solution for background size cover simulation using img tag with very good browser support (IE8+):
.container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
.container img {
position: absolute;
top: 50%;
left: 50%;
width: auto;
height: auto;
max-height: none;
max-width: none;
min-height: 100%;
min-width: 100%;
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
}
<div class="container">
<img src="//lorempixel.com/400/200/sports/1/" />
</div>
this may be easier
jQuery
$('.box').each(function() {
//set size
var th = $(this).height(),//box height
tw = $(this).width(),//box width
im = $(this).children('img'),//image
ih = im.height(),//inital image height
iw = im.width();//initial image width
if (ih>iw) {//if portrait
im.addClass('ww').removeClass('wh');//set width 100%
} else {//if landscape
im.addClass('wh').removeClass('ww');//set height 100%
}
//set offset
var nh = im.height(),//new image height
nw = im.width(),//new image width
hd = (nh-th)/2,//half dif img/box height
wd = (nw-tw)/2;//half dif img/box width
if (nh<nw) {//if portrait
im.css({marginLeft: '-'+wd+'px', marginTop: 0});//offset left
} else {//if landscape
im.css({marginTop: '-'+hd+'px', marginLeft: 0});//offset top
}
});
css
.box{height:100px;width:100px;overflow:hidden}
.wh{height:100%!important}
.ww{width:100%!important}
This should handle any size/orientation, and will not only resize, but offset the images. All without relative or absolute positioning.
made a fiddle: http://jsfiddle.net/filever10/W8aLN/
Also for what it's worth, the same effect can be produced by instead of setting "width" and "height" (setting them could break this approach btw):
min-width: 100%;
min-height: 100%;
or
min-width: (your desired percent of viewport width)vw;
min-height: (your desired percent of viewport height)vh;
with
overflow: hidden;
on the parent
:)
The idea is to make additional wrapper for image:
<div class="wrap">
<div class="inner">
<img src="http://placehold.it/350x150">
</div>
</div>
And use such CSS:
.wrap {
position: relative;
width: 100%;
height: 200px;
background: rgba(255, 0, 0, 0.3);
overflow: hidden;
}
.inner {
position: absolute;
min-width: 100%;
height: 100%;
left: 50%;
-moz-transform: translateX(-50%);
-o-transform: translateX(-50%);
-ms-transform: translateX(-50%);
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
}
.inner img {
position: absolute;
min-height: 100%;
min-width: 100%;
top: 50%;
left: 50%;
-moz-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
This is working example: https://jsfiddle.net/kr60jroe/
From https://developer.mozilla.org/en-US/docs/Web/CSS/background-size:
cover
This keyword specifies that the background image should be scaled to be as small as possible while ensuring both its dimensions are greater than or equal to the corresponding dimensions of the background positioning area.
So, you're either looking at making the width: 100% or the height: 100%, whichever will create an overlap within the parent div. So we can use the following logic:
var makeBackgroundCover = function (div) {
$(div + " img").css("height", "100%");
if ($(div + " img").width() < $(div).width()) {
$(div + " img").css({
"height": "auto",
"width": "100%"
});
}
}
The following fiddle shows this function working on both a horizontal and vertical image.
http://jsfiddle.net/2r5Cb/
Here is my approach:
//collect the nodes
var parent = $('.box');
var img = $('image', box);
//remove width and height attributes
img.removeAttr('width');
img.removeAttr('height');
//set initial width
img.attr('width', parent.width());
//if it's not enough, increase the width according to the height difference
if (img.height() < parent.height()) {
img.css('width', img.width() * parent.height() / img.height());
}
//position the image in the center
img.css({
left: parseInt((img.width() - parent.width())/-2) + 'px',
top: parseInt((img.height() - parent.height())/-2) + 'px'
});
FIDDLE
Here's a clean JavaScript function to do this and an example of implementation:
function backgroundCover(elementSizes, containerSizes) {
var elementRatio = elementSizes.width / elementSizes.height,
containerRatio = containerSizes.width / containerSizes.height;
width = null,
height = null;
if (containerRatio > elementRatio) {
width = Math.ceil( containerSizes.width );
height = Math.ceil( containerSizes.width / elementRatio );
} else {
width = Math.ceil( containerSizes.height * elementRatio );
height = Math.ceil( containerSizes.height );
}
return { width, height };
}
Here's an example of implementation:
HTML
<!-- Make sure the img has width and height attributes. The original image's width and height need to be set in order to calculate the scale ratio. -->
<div class="photo"><img src="photo.jpg" width="400" height="300"></div>
CSS
.photo {
position: relative;
overflow: hidden;
width: 200px;
padding-bottom: 75%; /* CSS technique to give this element a 4:3 ratio. */
}
.photo img {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
JavaScript
$( window ).on( 'resize', function() {
$( '.cover-photo' ).each( function() {
var img = $( 'img', this ),
imgWidth = img.attr( 'width' ),
imgHeight = img.attr( 'height' ),
containerWidth = $( this ).width(),
containerHeight = $( this ).height(),
newSizes = backgroundCover( { width: imgWidth, height: imgHeight }, { width: containerWidth, height: containerHeight } );
img.css( {
width: newSizes.width,
height: newSizes.height
} );
} );
} );
While reading the accepted answer, it strikes me that we simply test on whether the image is 'portrait' or 'landscape':
if (ih>iw) {//if portrait
In the case of the OP, that's right. But others might be dealing with rectangles and should take the aspect ratio of the container and the 'child'-image into consideration:
var int_container_width = parseInt( $_container.width() );
var int_container_height = parseInt( $_container.height() );
var num_container_aspect = int_container_width/int_container_height;
var int_image_width = parseInt( $_image.width() );
var int_image_height = parseInt( $_image.height());
var num_image_aspect = int_image_width/int_image_height;
if ( num_image_aspect > num_container_aspect){
num_scale = int_container_width/int_image_width * 100;
} else {
num_scale = int_container_height/int_image_height * 100;
}
This is a pure css solution. You can define a wrapper with:
div.cover {
position: fixed;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
}
and the img:
img.cover {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
min-width: 50%;
min-height: 50%;
overflow-x: hidden;
}
Here the live example:
http://codepen.io/ErwanHesry/pen/JcvCw
You can use this style to the image tag :"object-fit:cover;"
This link will support you also https://css-tricks.com/almanac/properties/o/object-fit/
If you want the image centered in the box without resizing the image, just use this code:
.box {
width: 100px;
height: 100px;
overflow: hidden;
position: relative;
}
.box img {
width: 413px;
height: 325px;
position: absolute;
left: 50%;
top: 50%;
}
If you are looking to resize the image to fit, use the following code:
.box {
width: 100px;
height: 100px;
}
.box img {
width: 100%;
height: auto;
}
This code will leave some white space if the image is wider than it is tall. If neither of these work, you could just set the image as a background and use background-size: cover;.
For anyone who happens across this answer as I did today looking for a solution that will work with landscape, portrait, rectangle, square, etc images and arbitrary container sizes, I have included my own code below.
This will also work responsively, you'll just need to run it again whenever the window resizes.
JSFiddle:
http://jsfiddle.net/66c43ao1/
HTML
<div class="test">
<div class="cover">
<img src="http://d2ws0xxnnorfdo.cloudfront.net/character/meme/cool-dog.jpg" width="590" height="590"/>
</div>
</div>
CSS
/* modify the width and height below to demonstrate coverage */
.test {
height: 300px;
position: relative;
width: 500px;
}
/* you will need the below styles */
.cover {
height: 100%;
left: 0;
overflow: hidden;
position: absolute;
top: 0;
width: 100%;
z-index: 1;
}
JS
$('.cover').each(function() {
var containerHeight = $(this).height(),
containerWidth = $(this).width(),
image = $(this).children('img'),
imageHeight = image.attr('height'),
imageWidth = image.attr('width'),
newHeight = imageHeight,
newWidth = imageWidth;
if (imageWidth < containerWidth) {
// if the image isn't wide enough to cover the space, scale the width
newWidth = containerWidth;
newHeight = imageHeight * newWidth/imageWidth;
}
if (imageHeight < containerHeight) {
// if the image isn't tall enough to cover the space, scale the height
newHeight = containerHeight;
newWidth = imageWidth * newHeight/imageHeight;
}
var marginLeft = (newWidth - containerWidth)/2;
var marginTop = (newHeight - containerHeight)/2;
image.css({
marginLeft : '-' + marginLeft + 'px',
marginTop : '-' + marginTop + 'px',
height : newHeight,
width : newWidth
});
});
You can of course use libraries such as Backstretch which do this same thing, but I found this solution to be better for my purposes (no increase in dependencies, lighter weight, etc).
I created a function below that should do it. I borrowed some of the logic from the accepted answer and adjusted it to work with any container by creating a ratio for image dimension : container dimension and then compared which is greater to figure which dimension to adjust. Also added a 'center' argument ('true' centers, false sets it to top/left).
I'm using CSS3 with the translateX/Y, but could get it working without it easily enough.
Here's the code:
var coverImage = function(wrap, center) {
if (typeof center === 'undefined') {
center = true;
}
var wr = $(wrap),
wrw = wr.width(),
wrh = wr.height();
var im = wr.children('img'),
imw = im.width(),
imh = im.height();
var wratio = wrw / imw;
var hratio = wrh / imh;
//Set required CSS
wr.css({'overflow' : 'hidden'});
im.css({'position' : 'relative'});
if (wratio > hratio) {
im.width(wrw);
im.css({'height' : 'auto'});
if (center) {
im.css({
'top' : '50%',
'transform' : 'translateY(-50%)'
});
}
} else {
im.height(wrh);
im.css({'width' : 'auto'});
if (center) {
im.css({
'left' : '50%',
'transform' : 'translateX(-50%)'
});
}
}
}
and checkout the jsfiddle to see it in action: https://jsfiddle.net/cameronolivier/57nLjoyq/2/
I made something could work to emulate a background-size:cover and background-position:center.
If you want to change the position just change the styles "top" an "left" of the img
CSS
.box{
overflow:hidden;
position:relative;
}
.box img{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
}
JS
$('.box').each(function() {
//aspect ratio of container
var boxRatio = $(this).height() / $(this).width();
//aspect ration of image
var imageRatio = $(this).children('img').height() / $(this).children('img').width();
//set width or height 100% depend of difference
if (imageRatio > boxRatio) {
$(this).children('img').css({"width":"100%","height":"auto"});
} else {
$(this).children('img').css({"height":"100%","width":"auto" });
}
});
This function should be activated on "load" and "resize" event.

Parallax scrolling effect, start at certain percentage on page?

I am using this script to create a parallax scroll effect on my page:
$(window).scroll(function (e) {
parallax();
});
function parallax() {
var scrolled = $(window).scrollTop();
$('.cloud1').css('top', - (scrolled * 0.1) + '%');
$('.cloud2').css('top', - (scrolled * 0.3) + '%');
$('.cloud3').css('top', - (scrolled * 0.2) + '%');
}
HTML:
<div class="cloud1"></div>
<div class="cloud2"></div>
<div class="cloud3"></div>
CSS (same for .cloud2 and .cloud3 but with different background image, opacity and 'top' 'left'):
.cloud1 {
background: url(../images/cloud1.png) no-repeat;
opacity: 0.9;
position: fixed;
width: 100%;
height: 100%;
top: 50%;
left: 20%;
z-index: 1;
}
When the script begins (on scroll) the HTML changes to this:
<div class="cloud1" style="top: 0%; "></div>
which makes the 'cloud' jump to the top of the page, and then the parallax starts (which you can see for a very short period of time as it's already jumped to the top of the page)
Is there a way to set the style="top: 0%;" to start at say 20% when the parallax begins, and then begin to multiply by 0.1?
Here is a codepen of the problem : http://codepen.io/anon/pen/tkfDH
Hopefully this is clear,
Any help is appreciated
Jon
Okay so I think i've fixed the problem.
$(window).scroll(function(e){
parallax();
});
function parallax(){
var scrolled = $(window).scrollTop();
$('.cloud1').css('top', -(scrolled*0.1)+70+'%');
// the 70 corresponds to the 'cloud1' value for 'top'.
$('.cloud2').css('top', -(scrolled*0.3)+50+'%');
// the 50 corresponds to the 'cloud2' value for 'top'.
}
http://cdpn.io/naIjf
#hero {
background:black;
color: white;
}
.cloud1, .cloud2 {
opacity: 0.8;
position: fixed;
width: 100%;
height: 100%;
z-index: 1;
}
.cloud1 {
background: url('http://www.jrk-design.co.uk/v2/images/big-cloud.png') no-repeat;
top: 70%;
left: 0;
}
.cloud2 {
background: url('http://www.jrk-design.co.uk/v2/images/big-cloud.png') no-repeat;
top: 50%;
left: 65%;
}
Fixed the jump.
Hope this helps.

Categories