秘银:渲染一个DOM元素基于另一个

时间:2016-12-20 19:53:13

标签: javascript css mithril.js

我有一个固定的高度div(body),包含两个孩子,标题和内容。单击按钮时标题的高度会发生变化,内容的高度应自动调整以填充身体的其余部分。现在问题是,在标题和内容div被渲染之后计算新标题的高度,因此内容div的高度不会在按钮点击时更新。这是缩短的代码:

return m('.body', {
  style: {
    height: '312px'
  }
}, [
  m('.header', /* header contents */),
  m('.content', {
    style: {
      height: (312 - this._viewModel._headerHeight()) + 'px'
    }
  }, /* some contents */)
])

headerHeight函数计算标题的高度并对其应用更改。然而,新高度是在渲染后计算出来的,因此不会立即应用于计算内容的高度 - 总是有滞后。

有什么想法来解决它吗?

1 个答案:

答案 0 :(得分:2)

在处理动态DOM布局时,这是一个常见问题,其中一些可写DOM属性是从其他可读DOM属性派生的。在像Mithril这样的声明式虚拟DOM习语中,这一点尤其难以理解,因为它们的前提是每个视图函数都应该是UI状态的自我完整快照 - 在这种情况下是不可能的。

您有3个选项:您可以通过直接操作Mithril视图之外的DOM来突破虚拟DOM惯用语来实现此功能,或者您可以对组件进行建模以在“2遍绘制”上进行操作,其中每个对header元素的潜在更改会导致1次绘制以更新标题,第二次绘制会相应地更新内容。或者,您可以使用纯CSS解决方案。

因为你只需要更新一个属性,所以你最好选择第一个选项。通过使用config函数,您可以编写在每次绘制视图之后执行的自定义功能。

return m('.body', {
  style: {
    height: '312px'
  },

  config : function( el ){
    el.lastChild.style.height = ( 312 - el.firstChild.offsetHeight ) + 'px'
  }
}, [
  m('.header', /* header contents */),
  m('.content', /* some contents */)
])

第二个选项在虚拟DOM哲学方面更具惯用性,因为它避免了直接的DOM操作,并将所有有状态数据保存在视图中读取和应用的模型中。当您拥有大量与DOM相关的动态属性时,这种方法会变得更有用,因为您可以在渲染视图时检查整个视图模型 - 但它也更加复杂和低效,特别是对于您的场景:

controller : function(){
  this.headerHeight = 0
},
view : function( ctrl ){
  return m('.body', {
    style: {
      height: '312px'
    }
  }, [
    m('.header', {
      config : function( el ){
      if( el.offsetHeight != ctrl.headerHeight ){
        ctrl.headerHeight = el.offsetHeight

        window.requestAnimationFrame( m.redraw )
      }
    }, /* header contents */),
    m('.content', {
      style : {
        height : ( 312 - ctrl.headerHeight ) + 'px'
      }
    }, /* some contents */)
  ])
}

第三个选项 - depending on which browsers you need to support - 将使用CSS flexbox module

return m('.body', {
  style: {
    height: '312px',
    display: 'flex',
    flexDirection: 'column'
  }
}, [
  m('.header', {
    style : {
      flexGrow: 1,
      flexShrink: 0
    }
  }, /* header contents */),
  m('.content', {
    style : {
      flexGrow: 0,
      flexShrink: 1
    }
  }, /* some contents */)
])

这样,您可以简单地声明容器是一个flexbox,标题应该增长以适应其内容并且永不缩小,并且内容应该缩小但不会增长。