I have a panel on the left side of my screen that flips out when activated. As you move your mouse around, it slightly alters the rotateY transform for a nice interactive effect. I want to mirror this on the right side of the screen, but every adjustment I make just causes the panel to freak out when you move the mouse.
What adjustments need to be made to the second jquery function to mirror the effect? I tried a lot of things including the current code which is replacing x = x + 15 with x = 360 - (x + 15). It's close, but still not right.
$(document).on('mousemove','#viewport1 .menu',function( event ) {
var x = Math.round(event.pageX / $(this).width() * 10);
x = x + 15;
$(this).css('transform','rotateY(' + x + 'deg)');
});
$(document).on('mousemove','#viewport2 .menu',function( event ) {
var x = Math.round(event.pageX / $(this).width() * 10);
x = 360 - (x + 15); //this is almost but not quite right...
$(this).css('transform','rotateY(' + x + 'deg)');
});
.viewport {
perspective: 1000px;
position: absolute;
top: 0; bottom:0; width: 30%;
padding: 5px;
}
.menu {
text-align: center;
width: 100%;
border: 1px solid black;
display: inline-block;
height: 100%;
}
#viewport1 {
left: 0;
}
#viewport1 .menu {
perspective-origin: left;
transform-origin: left;
transform: rotateY(15deg);
}
#viewport2 {
text-align: right;
right: 0;
}
#viewport2 .menu {
perspective-origin: right;
transform-origin: right;
transform: rotateY(345deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="viewport1" class="viewport">
<div class="menu">HOVER ME!</div>
</div>
<div id="viewport2" class="viewport">
<div class="menu">HOVER ME!</div>
</div>
You are using event.pageX, which is the position of the mouse pointer, relative to the left edge of the document, to calculate the rotation of the <div>. You need to substract the left offset: $(this).offset().left. After that you change x+15 to x-15 and you get the mirrored effect.
$(document).on('mousemove','#viewport1 .menu',function( event ) {
var x = Math.round(event.pageX / $(this).width() * 10);
x = x + 15;
$(this).css('transform','rotateY(' + x + 'deg)');
});
$(document).on('mousemove','#viewport2 .menu',function( event ) {
var x = Math.round((event.pageX - $(this).offset().left) / $(this).width() * 10);
x = x - 15;
$(this).css('transform','rotateY(' + x + 'deg)');
});
.viewport {
perspective: 1000px;
position: absolute;
top: 0; bottom:0; width: 30%;
padding: 5px;
}
.menu {
width: 100%;
border: 1px solid black;
display: inline-block;
height: 100%;
}
#viewport1 {
left: 0;
}
#viewport1 .menu {
perspective-origin: left;
transform-origin: left;
transform: rotateY(15deg);
}
#viewport2 {
text-align: right;
right: 0;
}
#viewport2 .menu {
perspective-origin: right;
transform-origin: right;
transform: rotateY(345deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="viewport1" class="viewport">
<div class="menu"></div>
</div>
<div id="viewport2" class="viewport">
<div class="menu"></div>
</div>
Related
Hi I am trying to create a carousel like object where you are able to click left and right to view between previous and next images using only HTML, CSS and JavaScript. Here is the code:
const container = document.querySelector(".container");
const lefty = document.querySelector(".lefty");
let translate = 0;
lefty.addEventListener("click", function() {
translate += 200;
container.style.transform = "translateX(" + translate + "px" + ")";
});
const righty = document.querySelector(".righty");
righty.addEventListener("click", function() {
translate -= 200;
container.style.transform = "translateX(" + translate + "px" + ")";
});
.outer {
overflow-x: hidden;
overflow-y: hidden;
}
.container {
display: flex;
transition: transform .4s ease-in;
}
.inner {
flex: 0 0 25%;
height: 100px;
margin: 10px;
}
.paddle {
position: absolute;
top: 50px;
bottom: 0;
width: 30px;
height: 20px;
}
.lefty {
left: 0;
z-index: 1;
}
.righty {
right: 0;
z-index: 1;
}
<button class="lefty paddle" id="left-button"></button>
<div class="outer" id="content">
<div class="container">
<div class="inner" style="background:red"></div>
<div class="inner" style="background:green"></div>
<div class="inner" style="background:blue"></div>
<div class="inner" style="background:yellow"></div>
<div class="inner" style="background:orange"></div>
</div>
</div>
<button class="righty paddle" id="right-button"></button>
The only issue I am having with this is that you can scroll way past the final div color block. Is it possible so that you can't scroll past the first and final div?
CodePen https://codepen.io/laurentkosc1990/pen/eYNXaxG
There are many approaches to solving this. In this example I define a max and min value that you can transform and check against that to stop the carousel. Either this can be fixed values or they can be dynamic to let you add and remove inner modules freely.
static
let minX = -400
let maxX = 0
dynamic
//number of inner classes times length, negate the visible classes
let minX = (inn.length * -200) + 600
const container = document.querySelector(".container");
const inn = document.getElementsByClassName('inner');
const lefty = document.querySelector(".lefty");
let translate = 0;
//let minX = -400
//number of inner classes times length, negate the visible classes
let minX = (inn.length * -200) + 600
let maxX = 0
lefty.addEventListener("click", function() {
if(translate >= maxX){
return;
}
translate += 200;
container.style.transform = "translateX(" + translate + "px" + ")";
});
const righty = document.querySelector(".righty");
righty.addEventListener("click", function() {
if(translate <= minX){
return;
}
translate -= 200;
container.style.transform = "translateX(" + translate + "px" + ")";
});
.outer {
overflow-x: hidden;
overflow-y: hidden;
}
.container {
display: flex;
transition: transform .4s ease-in;
}
.inner {
flex: 0 0 25%;
height: 100px;
margin:10px;
}
.paddle {
position: absolute;
top: 50px;
bottom: 0;
width: 30px;
height:20px;
}
.lefty {
left: 0;
z-index:1;
}
.righty{
right: 0;
z-index:1;
}
<button class="lefty paddle" id="left-button"></button>
<div class="outer" id="content">
<div class="container">
<div class="inner" style="background:red"></div>
<div class="inner" style="background:green"></div>
<div class="inner" style="background:blue"></div>
<div class="inner" style="background:yellow"></div>
<div class="inner" style="background:orange"></div>
</div>
</div>
<button class="righty paddle" id="right-button"></button>
maybe this will work for you
const container = document.querySelector(".container");
const visibleWidth = container.offsetWidth; // visible width of container
const fullWidth = container.scrollWidth; //width of container incliding hidden part
const innerDivWidth = document.querySelector(".inner").offsetWidth+20;// margin 10px from both sides
const lefty = document.querySelector(".lefty");
let translate = 0;
lefty.addEventListener("click", function() {
if(translate<0){
translate += innerDivWidth;
container.style.transform = "translateX(" + translate + "px" + ")";
}
});
const righty = document.querySelector(".righty");
righty.addEventListener("click", function() {
//here is my calculation, look carefully, you will get it
if((translate + fullWidth) > visibleWidth){
translate -= innerDivWidth;
container.style.transform = "translateX(" + translate + "px" + ")";
}
});
// btw translation rate should be according to inner div's width
// otherwise last div will not show properly
.outer {
overflow-x: hidden;
overflow-y: hidden;
}
.container {
display: flex;
transition: transform .4s ease-in;
}
.inner {
flex: 0 0 25%;
height: 100px;
margin: 10px;
}
.paddle {
position: absolute;
top: 50px;
bottom: 0;
width: 30px;
height: 20px;
}
.lefty {
left: 0;
z-index: 1;
}
.righty {
right: 0;
z-index: 1;
}
<button class="lefty paddle" id="left-button"></button>
<div class="outer" id="content">
<div class="container">
<div class="inner" style="background:red"></div>
<div class="inner" style="background:green"></div>
<div class="inner" style="background:blue"></div>
<div class="inner" style="background:yellow"></div>
<div class="inner" style="background:orange"></div>
</div>
</div>
<button class="righty paddle" id="right-button"></button>
I'm trying to resize rotated (transformed with css) object with vanilla js. Object origin is center and can't be changed.
I've found a function here which should do the thing, however still it doesn't looks ideal - left top corner change its position (half of pixels or so).
Here is a simplified codepen example.
What code modifications I need to do at getCorrection to keep left top corner position always same?
UPDATE:
As per comments below, the calculation is accurate but browser just can't deal with fractions of a pixel perfectly which seems like a technical limitation? Any ideas how to fix?
If you apply the transforms relative to the top left corner (transform-origin: top left), then you don't need to calculate that correction factor. You can then wrap all the shapes if you need to position them on the page:
const obj = document.querySelector('.object');
const btn = document.querySelector('button');
let h = 100;
let w = 100;
btn.addEventListener('click', () => {
h += 5;
w += 5;
updateElement();
});
function updateElement() {
obj.style.width = w + 'px';
obj.style.height = h + 'px';
}
updateElement();
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
overflow-y: scroll;
overflow-x: hidden;
}
button {
margin: 8px;
padding: 8px;
font-family: monospace;
border: 2px solid black;
background: transparent;
}
.container {
position: relative;
width: 100%;
height: 100%;
}
.object-initial {
position: absolute;
top: 16px;
left: 30%;
outline: 1px dashed black;
transform: rotate(20deg);
transform-origin: top left;
width: 100px;
height: 100px;
}
.object {
position: absolute;
top: 16px;
left: 30%;
outline: 1px solid black;
transform: rotate(20deg);
transform-origin: top left;
}
<button>Increase width and height</button>
<div class="container">
<div class="object"></div>
<div class="object-initial"></div>
</div>
Finally I was able to resolve issue with sub-pixels rendering. I've changed an approach slightly - instead of updating left & top coordinates with offset, I've used css translate() to apply the same offset. Works very well.
Seems transform has much better sub-pixels rendering optimizations.
Here is a codepen example.
function updateElement() {
obj.style.left = l + 'px';
obj.style.top = t + 'px';
obj.style.width = w + 'px';
obj.style.height = h + 'px';
obj.style.transform = 'rotate(' + r + 'deg) translate(' + ox + 'px, ' + oy + 'px)';
}
Same formula might be reused with slight adjustments to preserve any of rectangle corners.
I have created a div inside a container that points to the cursor. The issue is that the accuracy is off and I need to change the angle.
HTML
<div class="shooting_container">
<div class="shooter">
<div class="shooting_arm">
</div>
</div>
</div>
CSS
.shooter {
height: 480px;
width: 200px;
background-color: white;
bottom: 150px;
margin: 0 100px;
position: absolute;
.shooting_arm {
height: 245px;
width: 166px;
bottom: 500px;
position: absolute;
top: calc(50% - 40px);
transform: translateY(-50%) rotate(-12deg);
z-index: 500;
transform-origin: 156px 8px;
left: -43px;
background-color: red;
// Where the transform origin lies
&::after {
position: absolute;
top: 8px;
left: 156px;
width: 5px;
height: 5px;
content: '';
background-color: #f0f;
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
}
JS
$(document).on('mousemove', moveCursor);
function moveCursor(e) {
var box = $(".shooter .shooting_arm");
var boxCenter = [box.offset().left+box.width()/2, box.offset().top+box.height()/2];
var angle = Math.atan2(e.pageX - boxCenter[0], - (e.pageY - boxCenter[1]) )*(180/Math.PI) - 180;
if (angle < -160) {
angle = -0;
}
setArm(box, angle);
}
function setArm(arm, angle) {
arm.css({ "-webkit-transform": 'translateY(-50%) rotate(' + angle + 'deg)'});
arm.css({ '-moz-transform': 'translateY(-50%) rotate(' + angle + 'deg)'});
}
CodePen:
https://codepen.io/anon/pen/vrWJwZ
Problem 1: Angle is incorrect and differs as you move
Incorrect angle. I need the top of the container to align with the cursor, not the center. I tried adjusting:
var boxCenter = [box.offset().left+box.width()/2, box.offset().top+box.height()/2];
to
var boxCenter = [box.offset().left+box.width()/2, box.offset().top+box.height()/2/2];
But it still was not in line with the top of the container.
Problem 2: Glitches when cursor is within the div itself
For some reason it returns two different angle values when the cursor is within the container and causes it to jiggle. Why does it do this?
I have spent many days trying to make an item resizable that is rotated with interact.js.
This is the code that I have at this moment, I will try to explain the concept.
We have a selector item for two reasons, because the container could be scaled with css transform (like a zoom), and we need to have the selector outside and because we have a multiselection, and the selector grow if I have two rectangle selected, but in this case this is not the main problem and we have calculated the scaled proportion without problems and other things.
When the selector is resize, it take the rectangle, and make the same with the width, height, left, top and rotation.
Javascript:
// TAP - CLICK EVENT (just for positioning the selector)
interact('#rectangle').on('tap', event => {
console.log('Tap Box!');
event.stopPropagation();
const $rectangleCloned = $('#rectangle').clone();
const previousTransform = $rectangleCloned.css('transform');
$rectangleCloned.css('transform', 'none');
$rectangleCloned.css('opacity', '0');
$rectangleCloned.css('display', 'block');
$('#container').append($rectangleCloned);
const values = $rectangleCloned[0].getBoundingClientRect();
// This is just a trick for fast implementation:
$('#selector').css('top', values.y);
$('#selector').css('left', values.x);
$('#selector').css('width', values.width);
$('#selector').css('height', values.height);
$('#selector').css('transform', previousTransform);
$rectangleCloned.remove();
return values;
});
interact('.pointer9').draggable({
max: 1,
onmove: event => {
const angleDeg =
Math.atan2(
centerRotate.posY - event.pageY,
centerRotate.posX - event.pageX
) *
180 /
Math.PI;
console.log(this.rotate);
const prevAngle = this.rotate - angleInitial;
const angle = parseInt(angleDeg) + prevAngle;
this.$rectangle.css({
transform: 'rotate(' + angle + 'deg)'
});
this.$selector.css({
transform: 'rotate(' + angle + 'deg)'
});
},
onstart: event => {
const data = event.interactable.getRect(event.target.parentNode);
this.centerRotate = {
posX: data.left + data.width / 2,
posY: data.top + data.height / 2
};
this.angleInitial =
Math.atan2(
centerRotate.posY - event.pageY,
centerRotate.posX - event.pageX
) *
180 /
Math.PI;
this.$rectangle = $('#rectangle');
this.$selector = $('#selector');
this.rotate = $rectangle.attr('angle') || 0;
},
onend: event => {
const $box = $('#selector');
const matrix = $box.css('transform');
const values = matrix
.split('(')[1]
.split(')')[0]
.split(',');
var a = values[0];
var b = values[1];
var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
$rectangle.attr('angle', angle);
}
});
interact('#selector')
.resizable({
// resize from all edges and corners
edges: {
left: true,
right: true,
bottom: true,
top: true
},
// keep the edges inside the parent
restrictEdges: {
outer: 'parent',
endOnly: true,
},
// minimum size
restrictSize: {
min: {
width: 100,
height: 50
},
},
inertia: true,
})
.on('resizemove', function(event) {
var target = event.target,
x = parseFloat($(target).offset().left) || 0,
y = parseFloat($(target).offset().top) || 0;
// update the element's style
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.left = x + 'px';
target.style.top = y + 'px';
$('#rectangle')[0].style.left = target.style.left;
$('#rectangle')[0].style.top = target.style.top;
$('#rectangle')[0].style.width = target.style.width;
$('#rectangle')[0].style.height = target.style.height;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
});
CSS:
#container {
width: 500px;
height: 400px;
top: 0;
left: 0;
position: absolute;
background-color: #CCC;
}
#rectangle {
top: 50px;
left: 50px;
width: 120px;
height: 60px;
background-color: red;
position: absolute;
}
#selector {
display: inline-block;
position: absolute;
pointer-events: none;
z-index: 9999;
top: -1000px;
/*Not showing at start*/
}
#selector .pointers {
display: inline-block;
position: absolute;
z-index: 2;
width: 10px;
height: 10px;
pointer-events: all;
}
#selector .pointers .point {
width: 10px;
height: 10px;
background-color: #fff;
border: 2px solid rgba(0, 0, 0, 0.9);
border-radius: 50%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#selector .pointers.pointer1 {
top: -5px;
left: -5px;
}
#selector .pointers.pointer2 {
bottom: -5px;
left: -5px;
}
#selector .pointers.pointer3 {
top: -5px;
right: -5px;
}
#selector .pointers.pointer4 {
bottom: -5px;
right: -5px;
}
#selector .pointers.pointer-north {
top: -5px;
left: calc(50% - 5px);
}
#selector .pointers.pointer-south {
bottom: -5px;
left: calc(50% - 5px);
}
#selector .pointers.pointer-east {
right: -5px;
top: calc(50% - 5px);
}
#selector .pointers.pointer-west {
left: -5px;
top: calc(50% - 5px);
}
#selector .pointer-rotate {
border: 2px solid rgba(0, 0, 0, 0.9);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 50%;
cursor: rotate;
}
#selector .pointer9 {
bottom: -70px;
left: calc(50% - 11px);
display: inline-block;
width: 20px;
height: 20px;
background-color: #fff;
pointer-events: all;
position: absolute;
}
#selector .rotate-line {
border-left: 1px dashed #5f5f5f;
height: 40px;
position: absolute;
top: -40px;
left: calc(50% - 1px);
width: 1px;
}
HTML:
<div id="container">
<div id="rectangle">
</div>
<div id="selector">
<div class="pointers pointer1">
<div class="point"></div>
</div>
<div class="pointers pointer2">
<div class="point">
</div>
</div>
<div class="pointers pointer3">
<div class="point">
</div>
</div>
<div class="pointers pointer4">
<div class="point">
</div>
</div>
<div class="pointers pointer-north">
<div class="point">
</div>
</div>
<div class="pointers pointer-east">
<div class="point">
</div>
</div>
<div class="pointers pointer-south">
<div class="point">
</div>
</div>
<div class="pointers pointer-west">
<div class="point">
</div>
</div>
<span class="topline lines-resize" />
<span class="rightline lines-resize" />
<span class="botline lines-resize" />
<span class="leftline lines-resize" />
<div class="pointer-rotate pointer9" />
<div class="rotate-line" />
</div>
</div>
Fiddle for testing:
https://jsfiddle.net/ub70028c/46/
I have read about other people trying to make the same without not results...
Thanks!
I checked your code and a similar library for resizable and rotatable and I figure out your problem.
First, checking similar library:
Please see this fiddle that I created by jquery.freetrans.js.
If you inspect on <div class="shape">, you can see
transform: matrix(1, 0, 0, 1, 0, 0);
If you rotate it, transform changed like below:
transform: matrix(0.997373, -0.0724379, 0.0724379, 0.997373, 0, 0);
In similar case, your code uses transform that at first, it doesn't transform and after rotating, it has like below:
transform: rotate(-2.49576deg);
If you can use matrix instead of rotate in transform, your code will work properly. If you can't change it, you can use similar library like jquery.freetrans.jsthat work properly with rotate and resize together.
https://github.com/taye/interact.js/issues/569
https://github.com/taye/interact.js/issues/499
https://github.com/taye/interact.js/issues/394
I am afraid you have chosen a library whose author has clearly stated his intent
There's no built-in way. As I mentioned in #137 I'm not really interested in handling scaled or rotated elements
So the question you should ask yourself is
Do I want to find a workaround to make this library work or choose a different library perhaps?
Update-1: 28-Apr-2018
In case you want to do it in canvas instead of normal elements then I found fabric.js a good option
we are very close to finish the work after five days... we need to optimice all the mathematical calculations... but yes, this is what I was looking for:
Sorry, but we don't have the code ready... I will post all with comments for other people.
Comments: For a mathematician, this task is not very complex because all the angles are rectangular (90º). I will try to make a PR to the Interact.js, even to other libraries to implement this feature by default. Hope this work help to other developers ;)
I was trying to move the divs (here it's question number) based on the prev and next button. So that the selected question is always visible on screen.
Here is the demo : http://jsfiddle.net/arunslb123/trxe4n3u/12/
Screen :
click and question number and click prev or next button to understand my issue.
My code :
$("#next")
.click(function () {
$(".c.current-question")
.each(function () {
var divIdx = $(this)
.attr('id');
var scrollTo = $('#' + divIdx)
.position()
.left;
$("#scrollquestion")
.animate({
'scrollLeft': scrollTo
}, 800);
});
});
$("#prev")
.click(function () {
$(".c.current-question")
.each(function () {
var divIdx = $(this)
.attr('id');
var scrollTo = $('#' + divIdx)
.position()
.left;
$("#scrollquestion")
.animate({
'scrollLeft': -scrollTo
}, 800);
});
});
Using scrollLeft is a bit tricky. I did a small redo of your use-case based on positioning and then moving it based on left of the container. The tricky part is to reliably calculate the negative position when scrolled to the extreme right. Also, need to take into account the widths and margins.
Check the below snippet:
var $wrap = $("#numWrap"), $strip = $("#strip"),
$leftArrow = $(".wrapper > .arrows").first(),
wrapWidth = $wrap.width() + $leftArrow.width(),
margin = 10;
fill(20); select($(".numberItem").first());
$strip.on("click", ".numberItem", function() { select($(this)); });
function select($elem) {
$(".numberItem").removeClass("selected");
$elem.addClass("visited").addClass("selected");
focus($elem[0]);
}
function focus(elem) {
var stripPos = $strip.position(),
numPos = $(elem).offset(),
elemWidth = $(elem).width() + margin,
numRight = numPos.left + elemWidth;
if (numRight > wrapWidth) {
$strip.css({"left": stripPos.left - elemWidth});
}
if (numPos.left < (margin + $leftArrow.width())) {
$strip.css({"left": stripPos.left + elemWidth});
}
}
$(".wrapper").on("click", "a.arrow", function() {
var stripPos = $strip.position();
if (this.id == "lft") {
$strip.css({"left": stripPos.left + (wrapWidth / 2)});
} else {
$strip.css({"left": stripPos.left - (wrapWidth / 2)});
}
});
$(".controls").on("click", "a.arrow", function() {
var $sel = $(".selected"), numPos, $sel, elemWidth;
$elem = $sel.length > 0 ? $sel.first() : $(".numberItem").first();
if (this.id == "lft") {
$sel = $elem.prev().length > 0 ? $elem.prev() : $elem;
select($sel);
} else {
$sel = $elem.next().length > 0 ? $elem.next() : $elem;
select($sel);
}
numPos = $sel.offset(); elemWidth = $sel.width() + margin;
numRight = numPos.left + elemWidth;
if (numPos.left > wrapWidth) {
$strip.css({"left": -($sel.text()) * $sel.width() });
}
if (numRight < 0) {
$strip.css({"left": +($sel.text()) * $sel.width() });
}
});
function fill(num){
for (var i = 1; i <= num; i++) {
var $d = $("<a href='#' class='numberItem'>" + i + "</a>");
$strip.append($d);
}
}
* { box-sizing: border-box; padding: 0; margin: 0; font-family: sans-serif; }
div.wrapper {
background-color: #ddd; width: 100vw; height: 64px;
clear: both; overflow: hidden; margin-top: 16px;
}
div.arrows {
float: left; width: 10%; min-width: 24px; height: 64px; line-height: 64px;
text-align: center; vertical-align: middle; overflow: hidden;
}
div.numWrap {
float: left; height: 64px; line-height: 64px;
width: 80%; vertical-align: middle;
overflow: hidden; position: relative;
}
div.strip {
position: absolute; left: 0px;
width: auto; white-space: nowrap;
transition: left 1s;
}
a.numberItem {
display: inline-block; text-align: center; margin: 0px 8px;
background-color: #fff; border-radius: 50%; width: 48px; height: 48px;
font-size: 1.2em; line-height: 48px; text-decoration: none;
}
a.numberItem.visited { background-color: #fff; color: #000; border: 2px solid #01aebc; }
a.numberItem.selected { background-color: #01aebc; color: #fff; }
div.controls { clear: both; }
div.controls > div.arrows { width: auto; margin: 0 12px; }
a, a:focus, a:active, a:link, a:visited {
display: inline-block;
text-decoration: none; font-weight: 600;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="arrows">
<a id="lft" class="arrow" href="#">〈</a>
</div>
<div id="numWrap" class="numWrap">
<div id="strip" class="strip"></div>
</div>
<div class="arrows">
<a id="rgt" class="arrow" href="#">〉</a>
</div>
</div>
<div class="controls">
<div class="arrows">
<a id="lft" class="arrow" href="#">〈 Previous</a>
</div>
<div class="arrows">
<a id="rgt" class="arrow" href="#">Next 〉</a>
</div>
<div>
Explanation:
Using absolute positioning on the number container, which is nested to get 100% width.
Markup:
<div class="wrapper">
<div class="arrows"><a id="lft" class="arrow" href="#">〈</a></div>
<div id="numWrap" class="numWrap">
<div id="strip" class="strip"></div> <!-- nesting here -->
</div>
<div class="arrows"><a id="rgt" class="arrow" href="#">〉</a></div>
</div>
CSS:
div.wrapper {
background-color: #ddd; width: 100vw; height: 64px;
clear: both; overflow: hidden; margin-top: 16px;
}
div.arrows {
float: left; width: 10%; min-width: 24px; height: 64px; line-height: 64px;
text-align: center; vertical-align: middle; overflow: hidden;
}
div.numWrap {
float: left; height: 64px; line-height: 64px;
width: 80%; vertical-align: middle;
overflow: hidden; position: relative; /* relatively positioned */
}
div.strip {
position: absolute; left: 0px; /* absolutely positioned */
width: auto; white-space: nowrap;
transition: left 1s; /* instead of jquery animate */
}
With this structure, we can now use left to control the scrolling.
For partially obscured numbers, try to gently focus-in (nudge into view) a number which is partially obscured. This can be done by checking the position relative to parent and adding the width/margin to it and also accounting for width of the left arrow (it might peep thru).
Javascript:
function focus(elem) {
var stripPos = $strip.position(),
numPos = $(elem).offset(),
elemWidth = $(elem).width() + margin,
numRight = numPos.left + elemWidth;
// if it is towards right side, nudge it back inside
if (numRight > wrapWidth) {
$strip.css({"left": stripPos.left - elemWidth});
}
// if it is towards left side, nudge it back inside
if (numPos.left < (margin + $leftArrow.width())) {
$strip.css({"left": stripPos.left + elemWidth});
}
}
Once the user has scrolled the list too far and then tries to click on previous / next buttons to select a question, then we need to move the entire container upto the selected number. We can easily do this by multiplying the question number with element width and then changing the left in positive (if towards right) or in negative (if towards left).
Javascript:
// if left of element is more than the width of parent
if (numPos.left > wrapWidth) {
$strip.css({"left": -($sel.text()) * $sel.width() });
}
// if right of element is less than 0 i.e. starting position
if (numRight < 0) {
$strip.css({"left": +($sel.text()) * $sel.width() });
}
Here is a fiddle to play with: http://jsfiddle.net/abhitalks/aw166qhx/
You will need to further adapt it to your use-case, but you get the idea.