Image Rotation with Fancybox - javascript

I'm creating an interface that allows users to rotate images 90 degrees counter clockwise. I rotate the image on the page using jquery and -webkit-transform, but I also want to update the preview of image in the Fancybox slideshow.
I tried rotating the image by doing the following:
$(".fancybox").fancybox({
afterShow: function(){
fancyboxRotation();
}
});
function fancyboxRotation(){
$('.fancybox-wrap').css('webkitTransform', rotate(-90deg));
$('.fancybox-wrap').css('mozTransform', rotate(-90deg));
}
But that ended up rotating the controls as well (and also placed the close button on the top left instead of the top right):
If I just apply the rotation to the image, the white border around it has the wrong orientation:
Anyone have experience applying transformations to a fancybox image?

For fancybox 3 here is what I came up with. It uses font awesome icons, you can replace with glyphicons or whatever else you choose.
//adding custom item to fancybox menu to rotate image
$(document).on('onInit.fb', function (e, instance) {
if ($('.fancybox-toolbar').find('#rotate_button').length === 0) {
$('.fancybox-toolbar').prepend('<button id="rotate_button" class="fancybox-button" title="Rotate Image"><i class="fa fa-repeat"></i></button>');
}
var click = 1;
$('.fancybox-toolbar').on('click', '#rotate_button', function () {
var n = 90 * ++click;
$('.fancybox-image-wrap img').css('webkitTransform', 'rotate(-' + n + 'deg)');
$('.fancybox-image-wrap img').css('mozTransform', 'rotate(-' + n + 'deg)');
});
});

You can rotate the outer most div in the fancy box content, In my case it's fancybox-skin(fancybox v2 )
afterShow: function(){
var click = 1;
$('.fancybox-wrap').append('<div id="rotate_button"></div>')
.on('click', '#rotate_button', function(){
var n = 90 * ++click;
$('.fancybox-skin').css('webkitTransform', 'rotate(-' + n + 'deg)');
$('.fancybox-skin').css('mozTransform', 'rotate(-' + n + 'deg)');
});
};

With help from #Ashik I finally got this working and did not have to give up showing the title since I instead rotate .fancybox-inner and overwrite some CSS so I can keep the white border. I also initialize the fancybox from the $(document).ready() function so I had to bind the button a little different.
Finally, this is kind of a long answer so let me know if I left something out since it is entirely possible! Also, we do not need to support IE (thank the lord), so it may or may not work correctly there.
I went ahead and removed the regular arrow and close buttons so they would stay put at the top. This requires that you add the fancy box button helper CSS and JS files:
<link href="/Content/css/jquery.fancybox.css" rel="stylesheet"/>
<link href="/Content/css/jquery.fancybox-buttons.css" rel="stylesheet"/>
<script src="/Content/Scripts/fancybox/jquery.fancybox.js"></script>
<script src="/Content/Scripts/fancybox/jquery.fancybox.pack.js"></script>
<script src="/Content/Scripts/fancybox/jquery.fancybox-buttons.js"></script>
Then initializing fancy box is being done from $(document).ready(), as I said, like below (notice I remove the arrows and close buttons and add them back in using the button helper's tpl property). In that tpl property I also create a custom rotation button with an onclick and a custom data-rotation property which will hold the current rotation:
$(document).ready(function() {
$(".fancybox").fancybox({
loop : true,
helpers: {
buttons: {
position: 'top',
tpl : '<div id="fancybox-buttons"><ul><li><a class="btnPrev" title="Previous" href="javascript:;"></a></li><li><a id="fancybox-rotate-button" title="Rotate" data-rotation="0" onclick="FancyBoxRotateButton()"></a></li><li><a class="btnNext" title="Next" href="javascript:;"></a></li><li><a class="btnClose" title="Close" href="javascript:jQuery.fancybox.close();"></a></li></ul></div>'
}
},
closeBtn: false, // you will use the tpl buttons now
arrows : false // you will use the tpl buttons now
});
Here is the custom rotation button's onclick function:
window.FancyBoxRotateButton = function() {
var fancyboxInner = $('.fancybox-inner');
var fancyBoxRotateButton = $('#fancybox-rotate-button');
var currentRotation = parseInt(fancyBoxRotateButton.data("rotation"));
var rotation = 'rotate(-' + (90 * ++currentRotation) + 'deg)';
fancyboxInner.css({
'-moz-transform' : rotation,
'-webkit-transform': rotation,
'-o-transform' : rotation,
'transform' : rotation
});
fancyBoxRotateButton.data("rotation", currentRotation.toString());
}
Last but not least we need to fix the white border and then I also change the size of the custom button ul and set my custom rotation button's picture. There is probably better ways to do this (if you know of one let me know!) but I simply removed .fancybox-skin's background and box shadow and added it to .fancybox-inner:
#fancybox-buttons ul{
width: 130px;
}
#fancybox-buttons #fancybox-rotate-button {
background-image: url('/Content/images/fancybox_rotate.png')
}
.fancybox-skin {
background: none !important;
}
.fancybox-opened .fancybox-skin {
-webkit-box-shadow: none !important;
-moz-box-shadow : none !important;
box-shadow : none !important;
}
.fancybox-inner {
border-radius : 4px;
border : 2px solid white;
padding : 10px;
background : white none repeat scroll 0 0;
-webkit-box-shadow: 0 10px 25px #000000;
-webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
-moz-box-shadow : 0 10px 25px #000000;
-moz-box-shadow : 0 10px 25px rgba(0, 0, 0, 0.5);
box-shadow : 0 10px 25px #000000;
box-shadow : 0 10px 25px rgba(0, 0, 0, 0.5);
}
Hope it helps someone!

You can rotate your image by applying the css style -webkit-transform: rotate(-90deg); only to the '.fancybox-inner' class element instead of the '.fancybox-wrap' so that it will rotate only the image and not the whole container that includes the controls and descriptions.

My solution was to remove the arrows and close button from the fancybox and fade in the fancybox slideshow after the rotation was applied to the entire fancybox-wrap. To do this, I set the display of the fancybox-wrap to none in the "beforeShow", and then on "AfterShow", I fade in the image. Here's my code:
$(".fancybox").fancybox({
helpers: {
overlay: {
locked: false
}
},
arrows: false,
closeBtn: false,
beforeShow: function(){
if($('.fancybox-image').length>0){
$('.fancybox-wrap').css('display', 'none');
var imageID = getFancyboxImageID();
var rotation = getFancyboxRotation(imageID);
if(rotation!=0){
fancyboxRotate(rotation);
}
}
},
afterShow: function(){
if($('.fancybox-image').length>0){
$('.fancybox-wrap').fadeIn();
}
}
});

I wanted to keep the close button and the caption so I did a little magic trick with CSS3 transform:
afterShow: function() {
var click = 0, deg;
$('.fancybox-inner')
.append('<img id="rotate_button" src="https://cdn0.iconfinder.com/data/icons/super-mono-sticker/icons/button-rotate-cw_sticker.png" title="Rotate 90° CW">')
.on('click', '#rotate_button', function() {
click = (++click % 4 === 0) ? 0 : click;
deg = 90 * click;
$('.fancybox-wrap').css('transform', 'rotate(' + deg + 'deg)');
$('#rotate_button').css('transform', 'rotate(-' + deg + 'deg)');
sessionStorage.setItem('prev_rotated_image', $('.fancybox-image').prop('src'));
sessionStorage.setItem($('.fancybox-image').prop('src'), deg);
// move the close button and rotate the label
switch (deg) {
case 90:
$('.fancybox-close').css('transform', 'translate(-' + $('.fancybox-wrap').width() + 'px, 0px)');
$('.fancybox-title').find('span.child').css('transform', 'translate(' + ($('.fancybox-wrap').width() / 2 + $('.fancybox-title').height() / 2 + 8) + 'px, -' + ($('.fancybox-wrap').height() / 2) + 'px) rotate(-' + deg + 'deg)');
break;
case 180:
$('.fancybox-close').css('transform', 'translate(-' + $('.fancybox-wrap').width() + 'px, ' + $('.fancybox-wrap').height() + 'px)');
$('.fancybox-title').find('span.child').css('transform', 'translate(0px, -'+ ($('.fancybox-wrap').height() + $('.fancybox-title').height() + 16) +'px) rotate(-' + deg + 'deg)');
break;
case 270:
$('.fancybox-close').css('transform', 'translate(0px, ' + $('.fancybox-wrap').height() + 'px)');
$('.fancybox-title').find('span.child').css('transform', 'translate(-' + ($('.fancybox-wrap').width() / 2 + $('.fancybox-title').height() / 2 + 8) + 'px, -' + ($('.fancybox-wrap').height() / 2) + 'px) rotate(-' + deg + 'deg)');
break;
case 0:
case 360:
default:
$('.fancybox-close').css('transform', 'translate(0px, 0px)');
$('.fancybox-title').find('span.child').css('transform', 'translate(0px, 0px) rotate(0deg)');
}
});
}

Thanks, #Paul for a great snippet, I have added some class changes (current slide) and CSS property that worked for my version of fancybox3. Might help someone.
Note: you can replace the "Rotate" text with an icon.
//adding custom item to fancybox menu to rotate image
$(document).on('onInit.fb', function (e, instance) {
if ($('.fancybox-toolbar').find('#rotate_button').length === 0) {
$('.fancybox-toolbar').prepend('<button id="rotate_button" class="fancybox-button" title="Rotate Image">Rotate</button>');
}
var click = 1;
$('.fancybox-toolbar').on('click', '#rotate_button', function () {
var n = 90 * ++click;
$('.fancybox-slide--current img').css('webkitTransform', 'rotate(-' + n + 'deg)');
$('.fancybox-slide--current img').css('mozTransform', 'rotate(-' + n + 'deg)');
$('.fancybox-slide--current img').css('transform', 'rotate(-' + n + 'deg)');
});
});

See this issue on Github: https://github.com/fancyapps/fancybox/issues/1100
Code of user seltix5 is working in fancybox v3. I tested it myself.
You just need to append his/her code to your fancybox.js file. It simply extends fancybox object with rotate functionality after event "onInit.fb". It also adds rotate buttons to top menu and smooth rotate animation.

Related

Mouse move with hover using JavaScript

Am trying to achieve this (built using webflow) animation and interaction when hovering on an element but am not able to do so. I've found this answer here but when I tried to refactor it with on hover function I still couldn't make it work.
Here's what I've tried.
// Maximum offset for image
var maxDeltaX = 50,
maxDeltaY = 50,
viewportWidth = 0,
viewportHeight = 0,
mouseX = 0,
mouseY = 0,
translateX = 0,
translateY = 0;
// Bind mousemove event to document
jQuery('.image-content-right').on('mousemove', function(e) {
// Get viewport dimensions
viewportWidth = document.documentElement.clientWidth,
viewportHeight = document.documentElement.clientHeight;
// Get relative mouse positions to viewport
// Original range: [0, 1]
// Should be in the range of -1 to 1, since we want to move left/right
// Transform by multipling by 2 and minus 1
// Output range: [-1, 1]
mouseX = e.pageX / viewportWidth * 2 - 1,
mouseY = e.pageY / viewportHeight * 2 - 1;
// Calculate how much to transform the image
translateX = mouseX * maxDeltaX,
translateY = mouseY * maxDeltaY;
jQuery('.cyan').css('transform', 'translate(' + translateX + 'px, ' + translateY + 'px)');
jQuery('.small-cyan').css('transform', 'translate(' + translateX + 'px, ' + translateY + 'px)');
jQuery('.small-darktangirine').css('transform', 'translate(' + translateX + 'px, ' + translateY + 'px)');
}).hover(function() {
jQuery('.cyan').css('transform', 'translate(' + translateX + 'px, ' + translateY + 'px)');
jQuery('.small-cyan').css('transform', 'translate(' + translateX + 'px, ' + translateY + 'px)');
jQuery('.small-darktangirine').css('transform', 'translate(' + translateX + 'px, ' + translateY + 'px)');
})
It's a little bit clunky and not as smooth as what I want to achieve and also I would want it to go back to its original position when not hovered.
I'm not too sure how you'd really do much more to make that function a little smoother really considering its really depending on how often jQuery itself would execute its events. For now maybe I'd consider splitting all the code within your jQuery event declaration into their own respective functions. It'll be a lot easier and cleaner for you to work on :)
function animateElementOnMouseMove() {
// your translate code
}
function animateElementOnMouseHover() {
// your initial hover animation code
}
$('.image-content-right').on('mousemove', animateElementOnMouseMove)
.on('hover', animateElementOnMouseHover);
for it to return back to the position you had it at before you could either save an original untranslated position of each of the elements OR, you could save each of the translations into a count variable, then "undo" the translations after the element has become unfocused.
like:
var elementTranslateCountX = 0;
var elementTranslateCountY = 0;
// ON TRANSLATE
elementTranslateCountX += translateX;
elementTranslateCountY += translateY;
By the looks and feel of the webflow thing (if I understand your goal correctly) you want to be able to move your object by the full maxDeltaX/Y within the hover area. If that's the case, your math needs some adjustments: you need to define an origin (the center of the moving object most likely) and normalize to [-1, 1] the hover area around the origin. Placing the object in the dead center of the hover box simplifies calculations. I'm posting the code in a snippet, but it should be run on a full page because the coordinates are not correctly calculated. Funnily enough, if I run it on codepen, it works as expected on Chrome, but not on Safari. To avoid this issue you should wrap everything in a parent div and calculate coordinates relative to it
const page = document.getElementById("page-id");
const area = document.getElementById("area-id");
const box = document.getElementById("box-id");
// we want to move the object by 50px at most
const maxDeltaX = 50;
const maxDeltaY = 50;
let pageBox = page.getBoundingClientRect();
let pageTopLeft = {
x: pageBox.x,
y: pageBox.y
};
let areaBox = area.getBoundingClientRect();
let areaRange = {
w: areaBox.width / 2.0,
h: areaBox.height / 2.0
};
let boxBox = box.getBoundingClientRect();
let transformOrigin = {
x: boxBox.x + (boxBox.width / 2.0),
y: boxBox.y + (boxBox.height / 2.0)
};
// multipliers allow the full delta displacement within the hover area range
let multX = maxDeltaX / areaRange.w;
let multY = maxDeltaY / areaRange.h;
area.addEventListener("mousemove", onMove);
area.addEventListener("mouseleave", onLeave);
window.addEventListener("resize", onResize);
// mouse coords are computed wrt the wrapper top left corner and their distance from the object center is normalized
function onMove(e) {
let dx = (((e.clientX - pageTopLeft.x) - transformOrigin.x));
let dy = (((e.clientY - pageTopLeft.y) - transformOrigin.y));
box.style.transform = "translate3d(" + (dx * multX) + "px, " + (dy * multY) + "px, 0)";
/*
// or you can add some fancy rotation as well lol
let rotationDeg = Math.atan2(dy,dx) * (180/Math.PI);
let rotationString = "rotate(" + rotationDeg + "deg)";
box.style.transform = "translate3d(" + (dx * multX) + "px, " + (dy * multY) + "px, 0) " + rotationString;
*/
}
function onLeave(e) {
box.style.transform = "translate3d(0, 0, 0)";
}
function onResize(e) {
// redefine all the "let" variables
}
* {
margin: 0;
padding: 0;
}
.page {
position: relative;
width: 100%;
height: 100vh;
background-color: #ddd;
display: grid;
place-items: center;
}
.hover-area {
position: relative;
width: 50%;
height: 100%;
background-color: #888;
}
.box {
position: absolute;
left: calc(50% - 25px);
top: calc(50% - 25px);
width: 50px;
height: 50px;
border-radius: 25px;
background-image: linear-gradient(45deg, #000, #aaa);
transform: translate3d(0, 0, 0);
transition: all 0.2s;
will-change: transform;
}
<div id="page-id" class="page">
<div id="area-id" class="hover-area">
<div id="box-id" class="box" />
</div>
</div>
Note that is runs smoother on Chrome than on Safari. I'm not sure divs and css is the best way to go here for performance.
If I misunderstood the final result, explain more and I'll try to help.

How do I call a function every whatever seconds in Javascript?

Not sure why this is so hard to do in Javascript... Slightly frustrating LOL
Here's one of the ways I've tried to do it:
function rotateDavid() {
$("#david").css({
'transform' : 'rotate(90deg)'
});
setTimeout(rotateDavid, 10000);
};
rotateDavid();
It will do it once but doesn't repeat... I dunno...
The problem here is not how you are calling the function. This way is actually preferred over setInterval in some cases.
The issue you have is that setting the Css to 90degrees is not changing it over and over. You are setting it to the same degree value every time.
You need to update the angle on every iteration. So in this case you want to add 90 to it.
var rotation = 0;
function rotateDavid() {
rotation += 1
$("#david").css({
'transform' : 'rotate(' + (90 * rotation) + 'deg)'
});
setTimeout(rotateDavid, 1000);
};
rotateDavid();
div{
width:100px;
height: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="david">Hi</div>
You can also use a mod operator to keep the number from getting huge.
'transform' : 'rotate(' + (90 * (rotation%4)) + 'deg)'
Your method, actually, is called every 10s. You can check it if you add a log to the console inside the method. However, you was setting the css property always to the same value, so you won't see any visual effect. A possible fix is shown on next example:
function rotateDavid(rot)
{
$("#david").css({
'transform': `rotate(${rot}deg)`
});
rot = rot + 90 >= 360 ? 0 : rot + 90;
setTimeout(() => rotateDavid(rot), 5000);
};
rotateDavid(0);
#david {
background: skyblue;
width: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="david">David</div>
Even more, you can get similar functionality using setInterval():
function rotateDavid(rot)
{
$("#david").css({
'transform': `rotate(${rot}deg)`
});
};
var rot = 90;
setInterval(
() => {rotateDavid(rot); rot = rot + 90 >= 360 ? 0 : rot + 90;},
5000
);
#david {
background: skyblue;
width: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="david">David</div>

How to remove the delay in jQuery .hover() method

I tried to make my nav element change color as the document scrolls, I also want to make the hover state change color dynamically. But there's a delay, I have to wait for a fraction of seconds before I can hover and change the color. Can I remove the delay? Or better, when I am hovering on a menu, can I make the hovered color change by scrolling? I feel like I'm so close to the solution yet I can't find it.
Here are the jQuery codes:
$(document).ready(function () {
$(document).scroll(function () {
var h = $(window).scrollTop() / $(document).height() * 360;
if (h <= 180) {
hhover = h + 180;
} else {
hhover = h - 180;
}
$("a").css({
"color":"hsl(" + h + ",100%,50%)","transition":"0.2s ease"});
$("a").hover(
function () {
$(this).css(
"color", "hsl(" + hhover + ",100%,50%)");
},
function () {
$(this).css(
"color", "hsl(" + h + ",100%,50%)");
});
});
});
Please find my jsFiddle below:
https://jsfiddle.net/dtZDZ/1036/
Thank you!
In your CSS code:
.nav-links a:hover {
color: hsl(180,100%,50%);
transition: ease;
}
Delete all CSS transition definitions from javascript code and from styles.

jQuery background-position animation to run more smoothly

I have a pattern background animation in jQuery made like this.
banner.css('backgroundPosition', x + 'px' + ' ' + y + 'px');
window.setInterval(function() {
banner.css("backgroundPosition", x + 'px' + ' ' + y + 'px');
y--;
}, 150);
Live example http://codepen.io/anon/pen/emMxXa
But it is rather 'jittery'.
How can I make something like this to run more smoothly and slowly.
You want to use and look into CSS transitions and CSS animations for real smoothyness.
-webkit-animation: move 30s linear 0s infinite alternate;
-moz-animation: move 30s linear 0s infinite alternate;
#-webkit-keyframes move {
from { background-position: 0px 0px } to { background-position: 0px 400px }
}
#-moz-keyframes move {
from { background-position: 0px 0px } to { background-position: 0px 400px }
}
Demo: http://codepen.io/anon/pen/EaEMvy
Take a look at this jsFiddle.
You will see that by reducing the interval value from 150 to 30 it will be a lot smoother:
window.setInterval(function() {
banner.css("backgroundPosition", x + 'px' + ' ' + y + 'px');
y--;
//x--;
//if you need to scroll image horizontally -
// uncomment x and comment y
}, 30);
You can lower it even more but the more you lower it, the faster it gets also.
have a look at https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame
(function($) {
var x = 0;
var y = 0;
//cache a reference to the banner
var banner = $("#banner");
// set initial banner background position
banner.css('backgroundPosition', x + 'px' + ' ' + y + 'px');
// scroll up background position every 90 milliseconds
function step() {
banner.css("backgroundPosition", x + 'px' + ' ' + y + 'px');
y--;
//x--;
//if you need to scroll image horizontally -
// uncomment x and comment y
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step);
})(jQuery);

center image in simple jquery zoom plugin

I'm trying to write a very very simple zoom plugin that should have just a button to zoom in, zoom out, and the pan function to move the image around.
For now I've writte the part to zoom in and zoom out.
My problem is that I can't find a way to center the image inside the "zoombox".
This is my code so far:
$.fn.zoom = function() {
var img = this;
img.attr("style", "-ms-transform: scale(1); -ms-transform-origin: 0% 0%; -webkit-transform: scale(1); -webkit-transform-origin: 0% 0%").wrap('<div style="width: 400px; height: 400px; overflow: hidden;" class="zoombox" data-scale="1"></div>');
$("body").on("click.zoom", ".zoomin, .zoomout", function() {
if( $(this).hasClass("zoomin") ) {
var zoomFactor = (Number(img.parent().attr("data-scale")) + 0.1).toFixed(1);
} else {
var zoomFactor = (Number(img.parent().attr("data-scale")) - 0.1).toFixed(1);
}
img.parent().attr("data-scale", zoomFactor);
console.log(zoomFactor);
img.css({"-webkit-transform": "scale(" + zoomFactor + ")", "-ms-transform":"scale(" + zoomFactor + ")"});
});
};
Fiddle:
http://jsfiddle.net/xM7r4/1/
I know the style is not the best but I'm just trying to make it works without think about the style of the code.
How can I center the image inside the box, thinking that I will have to apply a pan effect later that will change the transform-origin values?
PS: I care about compatibility only on Chrome and IE9 for now.
edit for comment
You are correct. Here I've updated to work with transform-origin. It takes the dimensions of the containing div and divides by two (to get the centerpoint of the containing div) and passes these into the image's css transform-origin property:
http://jsfiddle.net/xM7r4/23/
I've tested with different dimensioned images, and it works.
original
You'll need to move the image using margin-left and margin-top depending on if you are zooming in or out.
http://jsfiddle.net/xM7r4/21/
Since you are increasing your image by a scale of 1%, you need to move the margins accordingly, negative for zoom-in, position for zoom-out.
$("body").on("click.zoom", ".zoomin, .zoomout", function() {
var imgWidth = $(img).width();
var imgHeight = $(img).height();
var scaleWidth = Math.floor(imgWidth * 0.01);
var scaleHeight = Math.floor(imgHeight * 0.01);
if( $(this).hasClass("zoomin") ) {
var zoomFactor = (Number(img.parent().attr("data-scale")) + 0.1).toFixed(1);
moveLeft -= scaleWidth;
moveTop -= scaleHeight;
} else {
var zoomFactor = (Number(img.parent().attr("data-scale")) - 0.1).toFixed(1);
moveLeft += scaleWidth;
moveTop += scaleHeight;
}
console.log(moveTop);
console.log(moveLeft);
img.parent().attr("data-scale", zoomFactor);
console.log(zoomFactor);
img.css({"-webkit-transform": "scale(" + zoomFactor + ")", "-ms-transform":"scale(" + zoomFactor + ")", "marginLeft": moveLeft, "marginTop": moveTop});
});

Categories