How to fix lags/clipping on mobile with a dynamic background-position? - angularjs

I'm working on a small app where the user move the background with his finger on the screen.
The background-position is defined by the position of the finger.
I'm using angular, so I have this kind of tags:
<div class="main-container" ng-touchmove="onTouchmove($event)">
<div class="image" ng-style="{ 'background-position': bgPosition }"></div>
</div>
And in my controller:
$scope.onTouchmove = function (e) {
$scope.bgPosition = e.touches[0].pageX + 'px ' + e.touches[0].pageY + 'px';
};
However, I have significant lags on mobile and the background is "clipping".
The issue seems to be common and people often suggest to use transform over background-position.
But how could I use transform if the position is based on the movements of the user? Globally, how can I avoid these lags?

I think you would want this:
$scope.bgPosition = e.touches[0].pageX + 'px ' + e.touches[0].pageY + 'px';
to be something like
$scope.transform = 'translate(' + e.touches[0].pageX + 'px, ' + e.touches[0].pageY + 'px)';
(and change { 'background-position': bgPosition } to { 'transform': transform} or similar.)
You will also may need to add another html element between .main-container and .image if you don't want to use main-container to crop the image. Something like
.crop-image {
overflow: hidden;
}
or
.crop-image {
clip: rect(/* dimensions offset from top and left. */)
}
(overflow might work for your needs, but if not check out https://developer.mozilla.org/en-US/docs/Web/CSS/clip and https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path).

Related

Is this a good time to use React refs?

I'm looking to recreate this elastic nav in a React component (see Codepen). Right now it's written in jQuery--and I'm having trouble getting the position and width in React. Would using React refs be a good way to access that information?
Any thoughts or insight would be greatly appreciated. I've been trying to figure out how to recreate this all week.
The click handler was pretty simple in React. I just created a function and then used onClick. And setting the active class wasn't too hard either. I ended up using a prop.
But these two sections of the code are where I'm stuck--and unsure how to recreate in React:
var activeItem = tabs.find('.active');
var activeWidth = activeItem.innerWidth();
$(".selector").css({
"left": activeItem.position.left + "px",
"width": activeWidth + "px"
});
var activeWidth = $(this).innerWidth();
var itemPos = $(this).position();
$(".selector").css({
"left":itemPos.left + "px",
"width": activeWidth + "px"
});
Here's the full JavaScript/jQuery:
var tabs = $('.tabs');
var selector = $('.tabs').find('a').length;
var activeItem = tabs.find('.active');
var activeWidth = activeItem.innerWidth();
$(".selector").css({
"left": activeItem.position.left + "px",
"width": activeWidth + "px"
});
$(".tabs").on("click","a",function(e){
e.preventDefault();
$('.tabs a').removeClass("active");
$(this).addClass('active');
var activeWidth = $(this).innerWidth();
var itemPos = $(this).position();
$(".selector").css({
"left":itemPos.left + "px",
"width": activeWidth + "px"
});
});
And here's the full HTML:
<div class="wrapper">
<nav class="tabs">
<div class="selector"></div>
</i>Popular
Upcoming
</i>My Movies
</i>Search
</nav>
</div>
And here's the link to the project with CSS (if you want to see it in action).
I'd love for this to be compartmentalized in a single component if possible. But if it needs to have some sub-components that's okay as well.
Thank you!
You can absolutely make this work in react, and you are on the right track.
You can use this syntax to create references to an element in React:
<input ref={input => this.myInput = input} />
Which can then be accessed by a handler tied to your tab links. For example:
handleTabClick = (e, index) => {
e.preventDefault()
position = {left: this.myInput.offsetLeft, top: this.myInput.offsetTop} // Make sure this is the correct JS!
width = this.myInput.offsetWidth // Make sure this is the correct JS!
}
render () {
return (
...
<a href="#" class="tab" onClick={e => handleClick(e, 1)}>Tab 1</a>
...
)
}
I'm not 100% sure about the element properties being accurate (width, position), but you can do a quick google/search on stackoverflow to find the correct ways to do it in vanilla JS instead of jQuery. You can also use this site for native JS methods which accomplish the same as jQuery: http://youmightnotneedjquery.com/

How to pass a variable to css in AngularJS

I'm trying to make a virtual scroll and whenever the user scrolls down I need to add a negative top equal to the container height to each row. But of course this top property can vary depending of some factors like the user's screen resolution or browser window size.
So far this is what I got:
<div class="container" id="my-container">
<!--If it has the class row-scrolled the top property is applied-->
<div ng-repeat="(row) in virtualCollection"
ng-class="{'row-scrolled': controller.isScrolled}">
<!-- row properties -->
</div>
</div>
I have also thought about the idea of using ng-style but would override any style from my .css file.
Is there anyway to get the size/property of a DOM element...
// controller
var containerHeight = angular.element('#my-container')[0].clientHeight;
var cssProperty = '-' + containerHeight + 'px';
And then use it in an css?
// css
.row-scrolled {
top: cssProperty;
}
You can't pass variables from javascript to CSS since CSS is not a programming language but a style sheet language.
What you can do is manipulating specific elements with javascript.
Based on your code here is an example:
// controller
var containerHeight = angular.element('#my-container')[0].clientHeight;
var cssProperty = '-' + containerHeight + 'px';
var $$rowScrolled = document.querySelectorAll(".row-scrolled");
if ($$rowScrolled && $$rowScrolled.length > 0) {
for (var i = 0; i < $$rowScrolled.length; i++) {
var $rowScrolled = $$rowScrolled[i];
$rowScrolled.style.top = cssProperty;
}
}
With jQuery:
// controller
var containerHeight = angular.element('#my-container')[0].clientHeight;
var cssProperty = '-' + containerHeight + 'px';
var $rowScrolled = $(".row-scrolled");
if ($rowScrolled && $rowScrolled.length > 0) {
$rowScrolled.css("top", cssProperty);
}
You can not pass a variable to the CSS.
What you can do though is add the property directly using the ng-style tag:
<div class="container" id="my-container">
<!--If it has the class row-scrolled the top property is applied-->
<div ng-repeat="(row) in virtualCollection"
ng-style="{'top': controller.cssProperty}">
<!-- row properties -->
</div>

how to implement Draggable element in ionic v1

Hi Guys My requirement is to make an element draggable across the screen and set the position of the element to the place where users stops dragging. So far I am able to make a element draggable across the screen but once released it is going back to its old position (position where it was earlier) I am using ngDraggable directive of angular. Sorry I am new to Ionic and angular. Any help will be highly appreciated.
My code goes as follows
<div ng-drag="true" id="draggableAxis" ng-style="{{draggedStyle}}" ng-drag-success="onDragComplete($data,$event)">
<img src="img/axis.png" >
<img ng-src="img/other.png" style="width:75px">
</div>
In my controller:
$scope.draggedStyle = {top: '96px',
right: '90px'};
var onDraggableEvent = function(evt, data) {
if(evt.name=='draggable:start'){
console.log('draggable-start');
console.log(data.x);
console.log(data.y);
}else{
console.log('draggable-end');
console.log(data);
console.log(data.element.centerX +' '+data.element.centerY);
console.log(evt);
$scope.setPosition(data);
}
}
$scope.onDragComplete=function(data,evt){
console.log("drag success, data:", data);
console.log(evt);
}// this fn doesnot gets triggered
$scope.$on('draggable:start', onDraggableEvent);
$scope.$on('draggable:end', onDraggableEvent);
$scope.setPosition=function(data){
$scope.draggedStyle = {
top: data.x+'px',
right: data.y+'px'
};
}
SCREEN SHOT OF MOBILE VIEW
I updated the fix to https://plnkr.co/edit/fVaIUVvAd7jLETkwVepm?p=preview
One of the problems was
ng-style="{{draggedStyle}}"
which should be
ng-style="draggedStyle"
Also, I switched the setPosition method to flip the x and y and used left because x indicates position from the left and not right.
$scope.setPosition = function(data) {
$scope.draggedStyle = {
top: data.y + 'px',
left: data.x + 'px'
};
}
Hope that helps

angular code not working in dynamically generated content

I know I've had this happen before but not in exactly this way and I'm having trouble making the necessary changes.
I have a div that starts out empty and hidden as part of a gallery. When I click an image, it populates the div and shows it. Along with the image/content, there's some navigation elements as well. Thing is, those are also dynamically generated and not working since the angular needs to recompile. Here's the function that populates:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
pic = match[0].image;
title = match[0].title;
desc = match[0].desc;
closediv = "<div id=\"divClose\" class=\"floatRight\" ng-click=\"closeParent();\">Close</div>";
navDiv = "";
if(picID > 1){
prev = picID - 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + prev + ")\">Previous</div>";
}
if(picID < $scope.picCount){
next = picID + 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + next + ");\">Next</div>";
}
$('#innerPictureDisplay').html(closediv + "<br/><br/><img src=\"images/paintings/" + pic + "\" /><p><b>" + title + "</b><\p><p>" + desc + "</p>"+ navDiv);
$('#outerPictureDisplay').css('display','block');
};
How do I make that code "recompile" so that the "CLOSE" and navigational items work?
UPDATE
So, I changed my approach but I'm clearly still missing something:
my new HTML:
<div id="outerPictureDisplay" ng-show="picID > 0">
<div id="innerPictureDisplay">
<div id="divClose" class="floatRight" ng-click="picID = 0;">Close</div>
<div id="picNav" ng-click="picturePop({{prevID}});" ng-show="picID > 1">Previous</div>
<div id="picNav" ng-click="picturePop({{nextID}});" ng-show="picID < picCount">Next</div>
<img src="images/paintings/{{thisPic.image}}" />
<p>
<b>{{thisPic.title}}</b>
</p>
<p>{{thisPic.desc}}</p>
</div>
</div>
my new function:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
$scope.thisPic = match[0];
$scope.picID = picID;
$scope.nextID = picID + 1;
$scope.prevID = picID - 1;
var $content = $('#innerPictureDisplay');
var scope = $content.scope();
$compile($content.contents())(scope);
};
When I click on a pic, it all loads up fine but when I click on a Previous or Next, it closes since it's registering picID as null. I checked and the numbers are indeed getting printed in the code, but angular doesn't see them on the click. They're coming through as undefined - seemingly the compile issue. I tried implementing it all as a directive, even using the suggested link, but I must've done something off since it didn't help.
You can use the angular service $compile like so:
$compile("<a ng-click='myFunction()'>my html</a>")(scope);
And of course you must inject him wherever you are.
Anyway, for God sake, don't use DOM manipulation this way. You should use a directive and feed the content from a model instead.
UPDATE 1
I sugest use incuna's bindHtmlCompile directive that you can found here.

How to set the height of the body equals to the length of an array in Angular

I have this user list, which can grow dynamically if you add a new user. Now I want my whole body to be the length of the user list.
In my factory I determine the length of the array and now I need to know how to access/translate this to the view in angular. Something like this:
<div class="mainWrapper" id="mainView" style="width: {{gridSizeNG.x * 122}}px; height: {{gridSizeNG.y * the length of the user list }}">
...
</div>
Part of my factory code:
UserService.getUsers = function () {
$http.get("api/users") //your API url goes here
.success(function(dataFromServer){
//console.log('LOGGING DATADROMSERVER ', dataFromServer);
//UserService.userList = [];
/*dataFromServer.forEach(function(user, index, arr) {
UserService.userList.push(user);
})*/
var initials = function(name){
var d1 = name.split(" ")[0].charAt(0).toUpperCase();
var d2;
try
{
d2 = name.split(" ")[1].charAt(0).toUpperCase();
}
catch(e){
d2 = "";
}
return d1 + d2;
console.log('LOGGING INITIALS ', d1 + d2);
}
for (var i = 0; i < dataFromServer.length; i++) {
UserService.userList[i] = dataFromServer[i];
UserService.userList[i].initials = initials(UserService.userList[i].name)
};
console.log('#### logging lenght of the userlist ' , UserService.userList.length );
//here you should update the usersList from the server like this:
//UserService.usersList = dataFromServer;
return dataFromServer;
})
.error(function(errorFromServer){
//something went wrong, process the error here
console.log("Error in getting the users from the server");
})
};
return UserService;
})
As you can see from the image, the height of the body is now not dynamic
IMO the best approach here would be to use CSS to your advantage and have the size grow in accordance with the amount of elements you have. Which means (idealy) you don't need to set the height and width property in CSS, you only need to set the max-height and max-width properties, should the list grow more than the body.
If you do want to control the height and width using JS then use ng-style instead of style, keep in mind ng-style is considered a last resort in controlling styles, one should ideally use ng-class if possible.
With ng-style you will be able to use your scope variables to control the height and width.
Also try to use something like this, using your existing html code:
<div class="mainWrapper" id="mainView" style="width: {{gridSizeNG.x}} * 122 + 'px'; height: {{gridSizeNG.y}} * {{the length of the user list }} + 'px'">
...
</div>
Not sure if the above code will work, but ng-style will.

Resources