improve javascript animation to do it smooth and optimized - javascript

I need your help to improve my animation : it is a 3d hover effect on mouse move on some elements.
this animation is visually working but i'd like to improve it and optimize it (with requestAnimationFrame maybe ?) or something else, i'm a javascript novice and don't really understand how to do that...
there is a working exemple of my code, thanks for your help
function mouseCoordinates(event, element) {
const bounds = element.getBoundingClientRect()
const centerX = bounds.left + bounds.width / 2
const centerY = bounds.top + bounds.height / 2
return {
x: event.clientX - centerX,
y: event.clientY - centerY
}
}
function hover3d(event, element) {
const coordinates = mouseCoordinates(event, element)
let x = coordinates.x / 1000000
let y = coordinates.y / 500000
element.style.transform = 'matrix3d(1,0,0.00,' + x + ',0.00,1,0.00,' + y + ',0,0,1,0,0,0,0,1)'
}
const tiles = document.querySelectorAll('.tile__wrapper')
tiles.forEach(tile => {
tile.addEventListener('mousemove', function (e) {
hover3d(e, this)
})
tile.addEventListener('mouseout', function () {
this.style.transform = 'matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)'
})
})
.tiles {
display: flex;
flex-wrap: wrap
}
.tile {
margin-right: 20px;
margin-bottom: 20px;
}
.tile__wrapper {
transform-style: preserve-3d;
will-change: transform;
transition: transform .3s ease-out;
}
<div class="tiles">
<div class="tiles__item tile">
<div class="tile__wrapper">
<img src="https://picsum.photos/300/200" alt="">
</div>
</div>
<div class="tiles__item tile">
<div class="tile__wrapper">
<img src="https://picsum.photos/300/200" alt="">
</div>
</div>
<div class="tiles__item tile">
<div class="tile__wrapper">
<img src="https://picsum.photos/300/200" alt="">
</div>
</div>
<div class="tiles__item tile">
<div class="tile__wrapper">
<img src="https://picsum.photos/300/200" alt="">
</div>
</div>
</div>
Help would be appreciated!

Related

Animation back to it's starting point

I've made a small program to animate an image back and forth. Now the first jobbra button works well the image is animating picel by pixel to the right, but my back button balra doesn't work, the image doesn't move back to it's starting point. Is anyone has any idea why, please help. Thank you
var jobbAnim = 1;
var speed = 1;
var balAnim = 400;
function jobbra() {
if (jobbAnim < 400) {
jobbAnim = jobbAnim + speed;
}
document.getElementById("ilonakep").style.marginLeft = jobbAnim + "px";
window.requestAnimationFrame(jobbra);
}
function balra() {
if (balAnim >= 400) {
balAnim = balAnim - speed;
}
document.getElementById("ilonakep").style.marginRight = balAnim + "px";
window.requestAnimationFrame(balra);
}
<div id="container">
<div id="ilonakep">
<img src="/img/ilona.jpg" alt="ilona">
</div>
</div>
<p id="gombok">
<button onclick="balra()">Balra</button>
<button onclick="jobbra()">Jobbra</button>
</p>
You should use cancelAnimationFrame to make sure you only have one animation playing at a time, otherwise a new animation will be started every time you click one of the buttons. I also, suggest you animate the element's position (or better yet, use transform: translate();) rather than the margin. It isn't as ideal as using CSS transitions, but it makes it more intuitive.
var left = 0;
var speed = 1;
var anim;
function jobbra() {
if (anim) {
window.cancelAnimationFrame(anim);
}
if (left < 400) {
left = left + speed;
}
document.getElementById("ilonakep").style.left = left + "px";
anim = window.requestAnimationFrame(jobbra);
}
function balra() {
if (anim) {
window.cancelAnimationFrame(anim);
}
if (left > 0) {
left = left - speed;
}
document.getElementById("ilonakep").style.left = left + "px";
anim = window.requestAnimationFrame(balra);
}
<div id="container">
<div id="ilonakep" style="position: relative; display: inline;">
<img src="/img/ilona.jpg" alt="ilona">
</div>
</div>
<p id="gombok">
<button onclick="balra()">Balra</button>
<button onclick="jobbra()">Jobbra</button>
</p>
Try this:
var jobbAnim = 1;
var speed = 1;
var balAnim = 400;
function jobbra() {
if (jobbAnim < 400) {
jobbAnim = jobbAnim + speed;
}
document.getElementById("ilonakep").style.marginLeft = jobbAnim + "px";
window.requestAnimationFrame(jobbra);
}
function balra() {
if (balAnim > 399) {
balAnim = balAnim - speed;
}
document.getElementById("ilonakep").style.marginLeft = balAnim + "px"; //This will reset the element to the left
document.getElementById("ilonakep").style.marginRight -= balAnim + "px"; //This will move it to the left
window.requestAnimationFrame(balra);
}
<div id="container">
<div id="ilonakep">
<img src="/img/ilona.jpg" alt="ilona">
</div>
</div>
<p id="gombok">
<button onclick="balra()">Balra</button>
<button onclick="jobbra()">Jobbra</button>
</p>
Using pure CSS animations, you can easily modify how it looks as well. Check out more here - https://www.w3schools.com/css/css3_animations.asp
function addLeft() {
document.getElementById("container").classList.add("slideLeft");
}
function addRight() {
document.getElementById("container").classList.add("slideRight");
}
.slideRight {
animation-name: slideRight;
animation-duration: 4s;
animation-fill-mode: forwards;
}
#keyframes slideRight {
from {
margin-left: 0;
}
to {
margin-left: 50%;
}
}
.slideLeft {
animation-name: slideLeft;
animation-duration: 4s;
animation-fill-mode: forwards;
}
#keyframes slideLeft {
from {
margin-left: 50%;
}
to {
margin-left: 0;
}
}
<div id="container">
<div id="ilonakep">
<img src="/img/ilona.jpg" alt="ilona">
</div>
</div>
<p id="gombok">
<button onclick="document.getElementById('container').classList.add('slideLeft')">Balra</button>
<button onclick="document.getElementById('container').classList.add('slideRight')">Jobbra</button>
</p>

Change CSS of inner div when scroll reaches that div

I am attempting to implement a scroll function where the CSS of the inner div's change when it reaches a certain height from the top.
var $container = $(".inner-div");
var containerTop = $container.offset().top;
var documentTop = $(document).scrollTop();
var wHeight = $(window).height();
var minMaskHeight = 0;
var descriptionMax = 200;
var logoMin = -200;
var maskDelta = descriptionMax - minMaskHeight;
var $jobOverview = $container.find(".right");
var $jobLogo = $container.find(".left");
var curPlacementPer = ((containerTop - documentTop) / wHeight) * 100;
var topMax = 85;
var center = 20;
var bottomMax = -15;
//console.log("Placement: " + curPlacementPer);
function applyChanges(perOpen) {
var maskHeightChange = maskDelta * (perOpen / 100);
var opacityPer = perOpen / 100;
var newDescriptionLeft = descriptionMax - maskHeightChange;
var newLogoLeft = logoMin + maskHeightChange;
if (newDescriptionLeft <= 0) newDescriptionLeft = 0;
if (newLogoLeft >= 0) newLogoLeft = 0;
if (opacityPer >= 1) opacityPer = 1;
$jobOverview.css({
transform: "translate(" + newDescriptionLeft + "%,-50%)",
opacity: opacityPer
});
$jobLogo.css({
transform: "translate(" + newLogoLeft + "%,-50%)",
opacity: opacityPer
});
}
if (window.innerWidth > 640) {
$container.removeClass("mobile");
// console.log("Placement: " + curPlacementPer);
if (curPlacementPer <= topMax /*&& curPlacementPer >= center*/ ) {
var perOpen = ((topMax - curPlacementPer) / 25) * 100;
applyChanges(perOpen);
} else if (curPlacementPer < center /*&& curPlacementPer >= bottomMax*/ ) {
var perOpen = (((bottomMax - curPlacementPer) * -1) / 25) * 100;
applyChanges(perOpen);
} else {
$jobOverview.css({
transform: "translate(200%,-50%)",
opacity: "0"
});
$jobLogo.css({
transform: "translate(-300%,-50%)",
opacity: "0"
});
}
<div class="outer-div">
<div class="inner-div first">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div second">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div third">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div fourth">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
Currently, all of the inner div's gets changed at the same time.
I noticed that when I change the $container class to equal '.first' and specify it more, it works.
Is there any way to make the inner div's change separately, relative to its height from the top? Any way I can iterate the scroll function so I can add more inner div's in the future and not have to worry about changing my scroll function?
In raw JavaScript, this is my answer:
// Define the element -- The '#fooBar' can be changed to anything else.
var element = document.querySelector("#fooBar");
// Define how much of the element is shown before something happens.
var scrollClipHeight = 0 /* Whatever number value you want... */;
// Function to change an element's CSS when it is scrolled in.
const doSomething = function doSomething() {
/** When the window vertical scroll position plus the
* window's inner height has reached the
* top position of your element.
*/
if (
(window.innerHeight + window.scrollY) - (scrollClipHeight || 0) >=
element.getBoundingClientRect().top
)
// Generally, something is meant to happen here.
element.style = "/* Yay, some CSS! */"
};
// Call the function without an event occurring.
doSomething();
// Call the function when the 'window' scrolls.
addEventListener("scroll", doSomething, false)
This is the method I use. If there are other methods, I'd love to see them as well but this is my answer for now.
consider using 3rd party jQuery plugin for easier job, like one of these:
https://github.com/xobotyi/jquery.viewport
or
https://github.com/zeusdeux/isInViewport
then you can have additional element selector e.g.: ":in-viewport"
so you can:
$(window).on('scroll',function() {
$('div').not(':in-viewport').html('');
$('div:in-viewport').html('hello');
});
Check if current scroll offset from top is bigger than the element offset from the top:
$(window).scroll(function() {
var height = $(window).scrollTop();
var element = $('#changethis'); //change this to your element you want to add the css to
if(height > element.offset().top) {
element.addClass('black'); //add css class black (change according to own css)
}
});
Html:
<div id="changethis">Test</div>
Css:
body
{
height:2000px;
}
.black
{
background-color:black;
color:white;
padding:20px;
}
Demo:
https://codepen.io/anon/pen/WZdEap
You could easily implement this in your existing code.
Below is the sample snippet code, Hope it'll work for you:
$(document).ready(function(){
topMax = 100;
topMin = 25;
$(document).scroll(function(){
$('.inner-div').each(function(){
if($(this).offset().top-$(window).scrollTop()<=topMax && $(this).offset().top-$(window).scrollTop()>=topMin){
$(this).css({'background':'#c7c7c7'});
}else{
$(this).css({'background':'inherit'});
}
});
});
});
div{
width:100%;
border:1px solid red;
padding:5px;
}
div.inner-div{
border: 1px dashed green;
height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="outer-div">
<div class="inner-div first">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div second">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div third">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div fourth">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
Happy to help you! :)

Angular js make slider with infinite effect

I created a simple photo slider but i does not how to make this infinite.
How can i make this effect with the angular way, please help.
I does not want to use jquery, but if it is the only way so whatever.
var app = angular.module('stack', []);
app.controller('MainCtrl', function($scope) {
$scope.images = ["http://lorempixel.com/600/200/sports/", "http://lorempixel.com/600/200/city/",
"http://lorempixel.com/600/200/nature/"
];
$scope.index = 0;
var IMG_WIDTH = -600;
$scope.next = function() {
++$scope.index;
if ($scope.images.length <= $scope.index) {
$scope.index = 0;
}
var pos = ($scope.index > 0) ? $scope.index * IMG_WIDTH : 0;
$scope.listPosition = {
transform: "translateX(" + pos + "px)"
};
}
$scope.prev = function() {
--$scope.index;
if ($scope.index < 0) {
$scope.index = $scope.images.length - 1;
}
var pos = ($scope.index > 0) ? $scope.index * IMG_WIDTH : 0;
$scope.listPosition = {
transform: "translateX(" + pos + "px)"
};
}
});
.mt {
margin-top: 2em;
}
.outer {
max-width: 600px;
overflow: hidden;
}
.slider {
width: 90000px;
position: relative;
transition: all 1s;
}
.slider div {
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link data-require="bootstrap#3.3.2" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
<body ng-app="stack" ng-controller="MainCtrl">
<div class="container-fluid">
<div class="row mt">
<div class="col-sm-6 col-sm-push-3">
<div class="outer clearfix">
<div class="slider clearfix" ng-style="listPosition">
<div ng-repeat="image in images track by $index">
<img ng-src="{{image}}" />
</div>
</div>
</div>
</div>
</div>
</div>
<a class="btn btn-default" ng-click="next()">Next</a>
<a class="btn btn-default" ng-click="prev()">Prev</a>
<div></div>
</body>
Easiest way would be to shuffle $scope.images array.
Once transition has been done, disable them, either by creating something like .no-transition class and adding it to the slider or by any other way you can imagine
$scope.images.push($scope.images.shift()) should put first item to last position, $scope.images.unshift($scope.images.pop()) should reverse it.
After that, you'll probably have to re-adjust transform value and re-apply transitions
Hope that helps.

Horizontal scroller edge detection

How would I modify my script so it will detect the edge and not scroll more than the container width?
Mark-up and JS included and also JSFiddle - http://jsfiddle.net/carlozdre/4HSLb/8/
<div id="content">
<div class="inner">
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
</div>
</div>
<div style="">
<a class="left" href="#">LEEEEFT</a>
<a class="right" href="#">RIGHHHT</a>
</div>
<style>
#content { float: left; width: 600px; overflow: scroll; white-space: nowrap; max-width: 3000px;}
.inner {width: 300px;}
</style>
$(function () {
$('.left').click(function (e) {
e.preventDefault();
$('.inner').animate({
marginLeft: "-=" + 20 + "px"
}, 'fast');
});
$('.right').click(function (e) {
e.preventDefault();
$('.inner').animate({
marginLeft: "+=" + 20 + "px"
}, 'fast');
});
});
You need to make a couple of changes to ensure the size of the inner container is fixed to the size of the images within, and then check the left and right position of the inner container.
You are also better to animate to a specific coordinate rather than adding or subtracting from the previous one, as it will handle fast clicks much better.
Working example: http://jsfiddle.net/4HSLb/13/
Markup:
<div id="content">
<div class="inner">
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
<img src="http://lorempixum.com/300/300" />
</div>
</div>
<div style=""> <a class="left" href="#">LEEEEFT</a>
<a class="right" href="#">RIGHHHT</a>
</div>
CSS:
#content {
float: left;
width: 610px;
overflow: hidden;
white-space: nowrap;
max-width: 3000px;
}
.inner {
background:#444;
height:300px;
}
.inner img {
float:left;
margin-right:10px;
}
Script:
var left = 0;
var contentWidth = 0;
var innerWidth = 0;
var imgCount = 0;
var imgWidth = 310;
$(function () {
contentWidth = parseInt($('#content').innerWidth());
left = parseInt($('.inner').css('margin-left'));
imgCount = $('.inner img').size()
innerWidth = parseInt(imgCount * imgWidth);
$('.inner').width(innerWidth + "px");
$('.left').click(function (e) {
e.preventDefault();
updatePos(imgWidth);
if (left <= 0) {
$('.inner').animate({
marginLeft: left + "px"
}, 'fast');
}
});
$('.right').click(function (e) {
e.preventDefault();
updatePos(0 - imgWidth);
if (left >= 0 - innerWidth + (imgWidth * 2)) {
$('.inner').animate({
marginLeft: left + "px"
}, 'fast');
}
});
});
function updatePos(distance) {
console.log("NewPos: " + (left + distance));
console.log(0 - innerWidth);
if (left + distance <= 0 && left + distance >= 0 - innerWidth + (imgWidth * 2)) {
left = left + distance;
}
//console.log(left);
}
Edit:
Updated to prevent over scrolling when fast clicking.
Edit 2:
Updated code to allow the number of images in view to be easily changed. Rather than editing the example code above, here is an example on jsFiddle: http://jsfiddle.net/4HSLb/14/

Dynamically place Unicode characters in a div with background image

I have an image that represents a weight-lifting bar:
I want to put the Full Block Unicode character (e.g. representing plates) on both the left and the right sides of the bar.
I'm trying to do this with 3 divs:
Barbell left: plates that go on the left side of the bar, right-justified
Barbell middle: nothing left blank
Barbell right: plates that go on the right side of the bar, left-justified
Here is a professional representation of the divs with a plate:
I thought I could do this with floats and percentages on div widths, but I'm not having any luck.
Here is my most recent attempt on js fiddle.
UPDATE
I got the answer I wanted, but based on the comment, I discovered that using Canvas was the better route.
I was able to achieve a better result with this html:
<div data-role="page" id="p1">
<div data-role="header"><h1>Header Page 1</h1></div>
<div data-role="content">
<div class="bar-canvas">
<canvas id="_barCanvas"></canvas>
</div>
</div>
</div>
<div data-role="footer"><h4>Footer</h4></div>
</div>
and this js:
var WIDTH_FACTOR = .8; //80% of screen size
var HEIGHT_FACTOR = .1; //10% of height size
var WEIGHT_SPACER = 2;
var ctx = $("#_barCanvas")[0].getContext("2d");
ctx.canvas.width = (window.innerWidth * WIDTH_FACTOR);
ctx.canvas.height = (window.innerHeight * HEIGHT_FACTOR);
var bar_width = ctx.canvas.width * .8;
var bar_height = ctx.canvas.height * .1;
var bar_x = (ctx.canvas.width - bar_width)
var bar_y = (ctx.canvas.height * .5)
var plate_stop_width = bar_width * .01;
var plate_stop_height = bar_height * 4;
var plate_stop_y = bar_y - ((plate_stop_height - (bar_y / 2)));
var rubber_plate_height = bar_height * 8;
var rubber_plate_y = (ctx.canvas.height / 2) - (rubber_plate_height/2) + (bar_height/2);
var small_plate_height = plate_stop_height;
var small_plate_y = plate_stop_y;
var left_plate_stop_x = bar_x + (bar_width * .3);
var right_plate_stop_x = bar_x + (bar_width * .7);
//Draw Bar
ctx.fillStyle = "black";
ctx.fillRect (bar_x, bar_y, bar_width, bar_height);
//Draw Plate stop left
ctx.fillStyle = "black";
ctx.fillRect (left_plate_stop_x, plate_stop_y, plate_stop_width, plate_stop_height);
//Draw Plate stop right
ctx.fillStyle = "black";
ctx.fillRect (right_plate_stop_x, plate_stop_y, plate_stop_width, plate_stop_height);
//Draw 45 lb Plates
var plate_width = bar_width * .04;
var current_plate_height = 0;
ctx.fillStyle = 'red';
ctx.fillRect(left_plate_stop_x - plate_width, rubber_plate_y, plate_width, rubber_plate_height);
ctx.fillStyle = 'red';
ctx.fillRect(right_plate_stop_x + plate_stop_width, rubber_plate_y, plate_width, rubber_plate_height);
I did it changing a bit your markup and css.
Demo (tested on Chrome 22 only)
HTML:
<div data-role="page" id="p1">
<div data-role="header"><h1>Header Page 1</h1></div>
<div data-role="content">
<div class="barbell-background">
<div class="barbell-left">█</div>
<div class="barbell-right">█</div>
</div>
</div>
<div data-role="footer"><h4>Footer</h4></div>
</div>
CSS:
.barbell-background
{
font-size:3em;
line-height:1.4em;
height:1.4em;
position:relative;
background-image:url('http://i.stack.imgur.com/ZmFY4.png');
background-repeat: no-repeat;
background-position: center;
}
.barbell-left, .barbell-right
{
position:absolute;
color:red;
}
.barbell-left
{
right:50%;
margin-right:146px;
}
.barbell-right
{
left:50%;
margin-left:145px;
}​
As Joachim Sauer said, it's probably easier and more consistent to just use divs for the red squares...
Another demo
HTML:
<div data-role="page" id="p1">
<div data-role="header"><h1>Header Page 1</h1></div>
<div data-role="content">
<div class="barbell-background">
<div class="barbell-left"></div>
<div class="barbell-right"></div>
</div>
</div>
<div data-role="footer"><h4>Footer</h4></div>
</div>
CSS:
.barbell-background
{
font-size:3em;
line-height:1.3em;
height:1.3em;
position:relative;
background-image:url('http://i.stack.imgur.com/ZmFY4.png');
background-repeat: no-repeat;
background-position: center;
}
.barbell-left, .barbell-right
{
position:absolute;
background:red;
width:0.5em;
height:100%;
}
.barbell-left
{
right:50%;
margin-right:146px;
}
.barbell-right
{
left:50%;
margin-left:144px;
}​
In both demos you'll see that a pixel "wobbles". Knowing the contents of the red square i could try to fix it.

Categories