1. The demand

The original menu needs to be modified in the recent project. The project UI is ANTD, and the navigation menu of ANTD looks like this:

It looked fine, perfectly aligned, until I filled in my menu text:

The left and right are out of alignment. It’s ugly. It’s offensive if left unchecked.

2. Troubleshoot problems

To start reviewing elements, look at the styles that the official demo aligns properly:

Take a look at the styles that didn’t align in my demo:

I found a min-width missing in my menu, which means antD added a style attribute to the official demo at some point, but not to my menu.

Why not give mine a plus!! ?

Start with a MutationObserver and see the MDN documentation for details.

< span style=” max-width: 100%; clear: both; min-width: 1em; Can you even make a breakpoint to debug, but don’t know how to do it? Directly on the code:

var ele = document.getElementById('item_2$Menu'Var config = {attributes:true, attributeFilter: ['style']}
var callback = function (mutationsList) {
  console.log(mutationsList)
}
var observer = new window.MutationObserver(callback)
observer.observe(ele, config)
Copy the code

Print the mutationsList whenever the item_2$Menu style property changes. So when I mouse over the menu, I print the following:

How does that help? Okay, so if you mouse over it, it will execute the code here, so why don’t you make a breakpoint?

AdjustWith (adjustWith, adjustWith, adjustWith, adjustWith, adjustWith, adjustWith) AdjustWith (adjustWith)

Looks like this code is the cause. AdjustWidth = adjustWidth () for node_modules /rc-menu/es/ submenu. js

this.adjustWidth = function () {
  /* istanbul ignore if* /if(! _this3.subMenuTitle || ! _this3.menuInstance) {return;
  }
  var popupMenu = ReactDOM.findDOMNode(_this3.menuInstance);
  if (popupMenu.offsetWidth >= _this3.subMenuTitle.offsetWidth) {
    return;
  }

  /* istanbul ignore next */
  popupMenu.style.minWidth = _this3.subMenuTitle.offsetWidth + 'px';
};
Copy the code

If the popupMenu width is greater than the Title width of the parent level, it will return directly. If the popupMenu width is smaller than the Title width of the parent level, the min-width attribute will be added.

It took so much trouble to find it!

But what happens when you find it? The question is how do I align? Since the text length is inconsistent, it is well centered.

3. Solve

There is a More options in rC-menu in the small corner of the Menu document. After clicking on it, I found a wider world (actually I asked my colleague before knowing that ANTD still depends on The react-component library is the antD implementation.

One of the props called builtinPlacements Describes how the popup menus should be positioned, and the parameter is DomA (sourceNode) and domB(targetNode); domA(sourceNode) and domB(targetNode);

const alignConfig = {
  points: ['tl'.'tr'],        // align top left point of sourceNode with top right point of targetNode
  offset: [10, 20],            // the offset sourceNode by 10px in x and 20px in y,
  targetOffset: ['30%'.'40%'], // the offset targetNode by 30% of targetNode width in x and 40% of targetNode height in y,
  overflow: { adjustX: true, adjustY: true }, // auto adjust position when sourceNode is overflowed
};

domAlign(domA, domB, alignConfig);
Copy the code

This aligns the upper-left corner (TL) of domA with the upper-right corner (TR) of domB. My intuition told me the builtinPlacements attribute would fix my alignment problem.

Then continue debugging, first introduce a debugging tool (colleagues told me). If you think about it, I used to debug a lot of front-end code in console.log, either in source, or node_modules (which I learned from a colleague), but the downside was that the code was compiled and packaged and not very readable. So there is a Vscode plugin Debugger for Chrome, with this plugin, you can directly in Vscode to the front-end js code break point! .

Next may be a jump:

Go directly to the rC-menu /es/submenu directory under node_modules, which is the menu component under React-Component. Search the folder for the word builtinPlacements, see where it’s placed. Here are the findings:

  1. rc-menu/es/submenu.js
. varbuiltinPlacements = props.builtinPlacements; . React.createElement( Trigger, { ...builtinPlacements: _extends({}, placements, builtinPlacements),
        ...
    }
Copy the code

The Submenu got the builtinPlacements passed in for the creation of the Trigger. Extraction

  1. rc-trigger/es/index.js
Trigger.prototype.getPopupAlign = function getPopupAlign() {... varbuiltinPlacements = props.builtinPlacements; .return getAlignFromPlacement(builtinPlacements, popupPlacement, popupAlign) }; . var align = _this5.getPopupAlign(); .return React.createElement(
      Popup,
      _extends({
        align: align,
    })
)
Copy the code

Ok, Popup is created again, but remember the getAlignFromPlacement(builtinPlacements, prefixCls, align, alignPoint) function:

  1. rc-trigger/es/Popup.js
import Align from 'rc-align'; . React.createElement( Align, { ... align: align ... }},Copy the code

Align = Align Align = Align Align = Align Align = Align Align = Align Align

  1. rc-align/es/Align.js
import { alignElement, alignPoint } from 'dom-align'; // You finally got out... var align = _this$props.align
...
if (element) {
  result = alignElement(source, element, align);
} else if (point) {
  result = alignPoint(source, point, align); }...Copy the code

Antd Menu => Rc-menu => Rc-trigger => Rc-align => dom-align…

The builtinPlacements parameter that you pass in the Menu is passed as a parameter to the getAlignPopupClassName(builtinPlacements, prefixCls, align, extraction, AlignPoint), the result is eventually passed to the alignElement or alignPoint of DOM-align.

But how does the builtinextraction parameter pass the value?

  1. function getAlignFromPlacement()

That is, we need to pass in an object like this:

builtinPlacements: {bottomLeft: {// alignConfig object points: ['tl'.'tr'], offset: [10, 20], ... }, leftTop: { ... }}Copy the code

PlacementStr in getAlignFromPlacement(builtinPlacements, placementStr, align) is at this point bottomLeft, so our Menu becomes:

<Menu builtinPlacements={
    {
        bottomLeft: 
        {
            points: ['tc'.'bc'], // Submenu"High"And title of the corresponding menu"In the"Alignment. overflow: { adjustX: 1, adjustY: 1 }, offset: [0, 5] } } }> {this.renderMenuItems(menuItems)} </Menu>Copy the code

The value of placementStr is bottomLeft:

rc-menu/es/SubMenu.js

var popupPlacementMap = {
  horizontal: 'bottomLeft',
  vertical: 'rightTop'.'vertical-left': 'rightTop'.'vertical-right': 'leftTop'}; var popupPlacement = popupPlacementMap[props.mode]; // This value ends up as"placementStr"Value of horizontal Menu"mode"for"horizontal"When,"placementStr"Is the"bottomLeft".Copy the code

I guess you can refer to Antd Popconfirm for the specific position diagram of bottomLeft and rightTop.

Final effect:

4. To summarize

This article is aimed at a menu component alignment problem encountered in the work, roughly talked about debugging ideas, ANTD component structure, DOM-align, MutationObserver and Debuggr for Chrome plug-in, involving code is not complex, I hope readers can have some gain after reading.

Thanks to my colleagues who know everything.