Learn from Zepto about “offset”

Time:2019-10-10

Preface

This article mainly talks about Zepto’s “offset” related things. Long, long ago, we often used it.offsetpositionscrollTopscrollLeftWhat are the differences between them and how to change the position of elements? Next, let’s unveil them bit by bit.

Original link

Source repository

Learn from Zepto about

offsetParent

offsetpositionImplementations within both APIs depend onoffsetParentMethod, let’s first see how it works.

Finding the first positioned ancestor element means that the position attribute in its CSS is “relative”, “absolute” or “fixed”# offsetParent

We all know that the CSS attribute position is used to specify how an element is positioned in a document, and its initial value isstaticCSS3 has even been addedstickyAnd so on, but at present it seems that browsers hardly support it.

Take a look at this example.

html

<div class="wrap">
  <div class="child1">
    <div class="child2">
      <div class="child3"></div>
    </div>
  </div>
</div>

css

<style>
  .wrap{
    width: 400px;
    height: 400px;
    border: solid 1px red;
  }

  .child1{
    width: 300px;
    height: 300px;
    border: solid 1px green;
    position: relative;
    padding: 10px;
  }

  .child2{
    width: 200px;
    height: 200px;
    border: solid 1px bisque;
  }

  .child3{
    width: 100px;
    height: 100px;
    border: solid 1px goldenrod;
    position: absolute;
    left: 0;
    top: 0;
  }
</style>

javascript

console.log($('.child3').offsetParent()) // child1
console.log(document.querySelector('.child3').offsetParent) // child1

Now that there is already one in its native formoffsetParentThe MDN offsetParent attribute is available for us to use. Why does Zepto implement one by itself? In fact, there are some differences between them. For example, in the same example, if the display attribute of child3 is set to none, the native offsetParent returns null, but Zepto returns Zepto objects containing body elements.

Source code analysis


offsetParent: function () {
  return this.map(function () {
    var parent = this.offsetParent || document.body
    while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
      parent = parent.offsetParent
    return parent
  })
}

The implementation of logic is still relatively simple, throughmapMethod traverses the currently selected set of elements, resulting in an array, each item being the nearest positioned ancestor element of the element.

First passoffsetParentNative DOM attributes are used to get the location element. If the default is not the body node, we can actually explain that the previous child3 setting isdisplay:noneIt returns null natively, but Zepto gets body.

var parent = this.offsetParent || document.body

Pass one morewhileCycle if

  1. Parent element exists
  2. Parent element is nothtmlperhapsbodyelement
  3. The display attribute of the parent element isstaticThe parent property is retrieved againoffsetParentCycle again.

offset

Gets the position of the current element relative to the document. Returns an object containing: top, left, width and height

Given an object with left and top attributes, these values are used to locate each element in the collection relative to the document.

  1. offset() ⇒ object
  2. offset(coordinates) ⇒ self v1.0+
  3. offset(function(index, oldOffset){ … }) ⇒

#offset

Source code

offset: function (coordinates) {
  if (coordinates) return this.each(function (index) {
    var $this = $(this),
      coords = funcArg(this, coordinates, index, $this.offset()),
      parentOffset = $this.offsetParent().offset(),
      props = {
        top: coords.top - parentOffset.top,
        left: coords.left - parentOffset.left
      }
    if ($this.css('position') == 'static') props['position'] = 'relative'
    $this.css(props)
  })

  if (!this.length) return null
  if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0]))
    return { top: 0, left: 0 }
  var obj = this[0].getBoundingClientRect()
  return {
    left: obj.left + window.pageXOffset,
    top: obj.top + window.pageYOffset,
    width: Math.round(obj.width),
    height: Math.round(obj.height)
  }
}

Similar to other APIs in Zeptoget one, set allIn principle, let’s first look at how acquisition operations are implemented.


if (!this.length) return null
if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0]))
  return { top: 0, left: 0 }
var obj = this[0].getBoundingClientRect()
return {
  left: obj.left + window.pageXOffset,
  top: obj.top + window.pageYOffset,
  width: Math.round(obj.width),
  height: Math.round(obj.height)
}

  1. !this.lengthIf the element is not currently selected, naturally there is no need to go down and return it directly.
  2. Not in the currently selected collectionhtmlElements, and nothtmlNode child elements. Direct return{ top: 0, left: 0 }
  3. The next logic is the point. Firstly, the size of the element and its position relative to the viewport are obtained by getBounding ClientRect. Then, the horizontal and vertical scrolled pixel values of the document are obtained by pageXOffset and pageYOffset, and the final desired values are obtained by adding them.

Look againSetup operationBefore realizing this, look at the following picture, which may help to understand.

Learn from Zepto about

if (coordinates) return this.each(function(index) {
  var $this = $(this),
      coords = funcArg(this, coordinates, index, $this.offset()),
      parentOffset = $this.offsetParent().offset(),
      props = {
        top: coords.top - parentOffset.top,
        left: coords.left - parentOffset.left
      }

  if ($this.css('position') == 'static') props['position'] = 'relative'
  $this.css(props)
})

Or that familiar pattern, familiar routine, loop through the current set of elements, easy to set one by one, through the funcArg function wrapped, so that the parameters can be either functions or other forms.

From the figure above, we should be able to see clearly if we want to set the child elements to the incoming one.coords.leftLocation, in fact

  1. Parent Offset. left relative to the left margin of the document
  2. Left margin between child elements and parent elements
  3. Add up and you get participation.coords.left

If we subtract, we will get the left and top values that we need to set through the CSS method.

Note that if the location attribute of the element isstaticThen it will be changed torelativeLocation is calculated relative to its normal document flow.

position

Gets the first element in the object set relative to itoffsetParentLocation.


position: function() {
  if (!this.length) return

  var elem = this[0],
    offsetParent = this.offsetParent(),
    offset = this.offset(),
    parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
  offset.top -= parseFloat($(elem).css('margin-top')) || 0
  offset.left -= parseFloat($(elem).css('margin-left')) || 0
  parentOffset.top += parseFloat($(offsetParent[0]).css('border-top-width')) || 0
  parentOffset.left += parseFloat($(offsetParent[0]).css('border-left-width')) || 0
  return {
    top: offset.top - parentOffset.top,
    left: offset.left - parentOffset.left
  }
}

Let’s start with an example.

html


<div class="parent">
  <div class="child"></div>
</div>

css


.parent{
  width: 400px;
  height: 400px;
  border: solid 1px red;
  padding: 10px;
  margin: 10px;
  position: relative;
}

.child{
  width: 200px;
  height: 200px;
  border: solid 1px green;
  padding: 20px;
  margin: 20px;
}

console.log($('.child').position()) // {top: 10, left: 10}

Below are the box models of the parent and child elements and the values of top that need to be retrieved.

Learn from Zepto about

Learn from Zepto about

Next let’s see how it works, come on!!!

  1. First step
var offsetParent = this.offsetParent(),
// Get correct offsets
// Gets the position of the current element relative to the document
offset = this.offset(),
// Get the location of the first location ancestor element relative to the document, if it is the root element (html or body), then 0, 0
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
  1. The second step
// The location relationship for the first location ancestor element should not include an example of margin, so subtract
offset.top -= parseFloat($(elem).css('margin-top')) || 0
offset.left -= parseFloat($(elem).css('margin-left')) || 0
  1. The third step
// Ancestor Location Element plus Border Width
parentOffset.top += parseFloat($(offsetParent[0]).css('border-top-width')) || 0
parentOffset.left += parseFloat($(offsetParent[0]).css('border-left-width')) || 0

The fourth step

// Subtraction is the result
return {
  top: offset.top - parentOffset.top,
  left: offset.left - parentOffset.left
}

The overall idea is to subtract the position of the first location ancestor element from the position of the current element relative to the document, but two points need to be noted are that the position API should not include the parent element.borderLength and subelementmarginSpace length. So there are two and three steps.

scrollLeft

Gets or sets the scrolling element on the page or the scrolling distance of the entire window to the right.


scrollLeft: function (value) {
  if (!this.length) return
  var hasScrollLeft = 'scrollLeft' in this[0]
  if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
  return this.each(hasScrollLeft ?
    function () { this.scrollLeft = value } :
    function () { this.scrollTo(value, this.scrollY) })
}

First, determine whether the currently selected element supports the scrollLeft feature.

If value doesn’t come in, support ithasScrollLeftProperty returns the first element’shasScrollLeftValue, which returns the first element if not supportedpageXOffsetValue.

PageXOffset is an alias for scrollX, and what it stands for isReturns the pixel value of the document/page scrolling horizontally

It’s coming in.valueThat’s setup operation, supportscrollLeftProperty, you can set its value directly, and scrollTo is used instead. Of course, when setting the horizontal direction, the vertical direction should be the same as before, so it is imported.scrollYAct as

scrollTop

Gets or sets the scrolling element on the page or the distance from which the entire window scrolls down.

scrollTop: function(value) {
  if (!this.length) return
  var hasScrollTop = 'scrollTop' in this[0]
  if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
  return this.each(hasScrollTop ?
    function() { this.scrollTop = value } :
    function() { this.scrollTo(this.scrollX, value) })
},

We can see the basic principles and patterns.scrollLeftConsistency, no longer one by one analysis.

Ending

These are the explanations of several of Zepto’s “migration” related apis, and you are welcome to point out the problems and errors.

Reference resources

Property operations for reading Zepto source code

scrollTo

scrollLeft

pageXOffset

Article record

Ie module

  1. Ie module of Zepto source code analysis (2017-11-03)

Data module

  1. Data Caching Principle and Implementation in Zepto (2017-10-03)

Form module

  1. Zepto Source Analysis Form Module (2017-10-01)

Zepto module

  1. Practical methodologies in these Zeptos (2017-08-26)
  2. Tools and Methods for Zepto Core Modules (2017-08-30)
  3. See how zepto can add, delete and modify DOM (2017-10-2)
  4. Zepto operates on element attributes like this (2017-11-13)
  5. Learn from Zepto about “offset” (2017-12-10)

Event module

  1. Why are mouseenter and mouseover so entangled? (2017-06-05)
  2. Learn from zepto. JS how to trigger DOM events manually (2017-06-07)
  3. Who says you just “know how to use” jQuery? (2017-06-08)

Ajax module

  1. So you are such a jsonp (principle and implementation details) (2017-06-11)

Recommended Today

Summary of MYSQL full backup, master-slave replication, cascade replication and semi-synchronization

MySQL full backup 1. Enable binary logs and separate them from the database and store them separately vim /etc/my.cnf Add to log_bin=/data/bin/mysql-bin Create / data / bin folder and authorize chown mysql.mysql /data/bin 2. Complete backup database mysqldump -A –single-transaction –master-data=2 | xz > /data/all.sql.xz 3. Adding, deleting and modifying the database INSERT hellodb.students(stuid,name,gender,age) VALUE(27,’Lujunyi’,’M’,30); […]