自动调整大小的固定文本以填充动态大小的容器

Auto size fixed text to fill dynamic size container

我正在尝试在 JavaScript 中重新创建 this game。对于这个游戏,我需要里面有数字的单元格。

我希望游戏的大小适合浏览器中可用的 space。正如您在示例中看到的,我已经设法通过将 vwvhwidthmin-width(以及高度)结合使用来做到这一点。如果调整显示单元格的视口大小,单元格也会随之调整大小。

问题

现在,我希望其中的文字也能随之调整大小。容器(单元格)会调整大小,数字的字体也应相应调整大小。我现在使用 vmax 作为一个单位,但这并没有考虑水平尺寸。由于没有 min-font-size,我无法对单元格本身使用相同的技巧。

没有jQuery请

我试过并搜索过。最值得注意的是,我发现 Auto-size dynamic text to fill fixed size container,但我认为我的问题是相反的。文本是固定的,我可以设置一个初始字体大小。我只需要字体随元素的大小一起缩放,所以也许这毕竟可以通过 CSS 完成。

此外,关于这个主题的大多数问题都建议使用各种 jQuery 插件之一,我并不是在寻找 jQuery 解决方案。我试图制作这个游戏只是为了好玩和练习,我设定了一个目标,即在没有 jQuery 的情况下创建它。实际上,我什至都没有在寻找普通的 JavaScript 解决方案。最后它可能归结为那个,但我还没有尝试自己构建它,所以我现在不想在这里请求 JavaScript。不,我正在寻找一个纯粹的 CSS 解决方案,如果有的话。

片段

精简版代码段在整页模式下效果最佳。不要介意内联样式。这些元素实际上是由 JavaScript 生成的,需要四处移动。对 HTML 的大块感到抱歉。一开始我把它减少到两个单元格,但这看起来很混乱,因为它们只占了屏幕的一小部分,你看不到发生了什么。

.game11,
.game11 .cell,
.game11 .cell .digit {
  box-sizing: border-box;
}
.game11 {
  width: 90vw;
  height: 90vw;
  max-width: 90vh;
  max-height: 90vh;
  box-sizing: border-box;
  position: relative;
}
.game11 .cell {
  width: 20%;
  height: 20%;
  position: absolute;
  font-size: 7vmax; /* Font size. This obviously doesn't work */
}
.game11 .cell .digit {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  border: 3px solid #666633;
  text-align: center;
  padding-top: 13%;
  font-family: Impact, Charcoal, sans-serif;
  color: #111111;
}
<div class="game11">
  <div class="cell" style="left: 0%; top: 0%;">
    <div class="digit digit2" style="top: 0px;">2</div>
  </div>
  <div class="cell" style="left: 20%; top: 0%;">
    <div class="digit digit2" style="top: 0px;">2</div>
  </div>
  <div class="cell" style="left: 40%; top: 0%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 60%; top: 0%;">
    <div class="digit digit1" style="top: 0px;">1</div>
  </div>
  <div class="cell" style="left: 80%; top: 0%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 0%; top: 20%;">
    <div class="digit digit1" style="top: 0px;">1</div>
  </div>
  <div class="cell" style="left: 20%; top: 20%;">
    <div class="digit digit1" style="top: 0px;">1</div>
  </div>
  <div class="cell" style="left: 40%; top: 20%;">
    <div class="digit digit4" style="top: 0px;">4</div>
  </div>
  <div class="cell" style="left: 60%; top: 20%;">
    <div class="digit digit1" style="top: 0px;">1</div>
  </div>
  <div class="cell" style="left: 80%; top: 20%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 0%; top: 40%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 20%; top: 40%;">
    <div class="digit digit2" style="top: 0px;">2</div>
  </div>
  <div class="cell" style="left: 40%; top: 40%;">
    <div class="digit digit4" style="top: 0px;">4</div>
  </div>
  <div class="cell" style="left: 60%; top: 40%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 80%; top: 40%;">
    <div class="digit digit4" style="top: 0px;">4</div>
  </div>
  <div class="cell" style="left: 0%; top: 60%;">
    <div class="digit digit2" style="top: 0px;">2</div>
  </div>
  <div class="cell" style="left: 20%; top: 60%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 40%; top: 60%;">
    <div class="digit digit5" style="top: 0px;">5</div>
  </div>
  <div class="cell" style="left: 60%; top: 60%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
  <div class="cell" style="left: 80%; top: 60%;">
    <div class="digit digit1" style="top: 0px;">1</div>
  </div>
  <div class="cell" style="left: 0%; top: 80%;">
    <div class="digit digit4">4</div>
  </div>
  <div class="cell" style="left: 20%; top: 80%;">
    <div class="digit digit1" style="top: 0px;">1</div>
  </div>
  <div class="cell" style="left: 40%; top: 80%;">
    <div class="digit digit2" style="top: 0px;">2</div>
  </div>
  <div class="cell" style="left: 60%; top: 80%;">
    <div class="digit digit5">5</div>
  </div>
  <div class="cell" style="left: 80%; top: 80%;">
    <div class="digit digit3" style="top: 0px;">3</div>
  </div>
</div>

更新:'full'(仍未完成)游戏,包括 Pangloss

建议的修复

在下面的代码片段中,您可以找到我目前拥有的游戏。它在很大程度上起作用,所以如果它对问题没有帮助,至少它可能对未来的访问者很有趣或有帮助。

/**
 * Game11 class
 */
function Game11(container) {
  var game = this;
  game.element = container;
  game.cells = [];
  game.highestValue = 4;
  game.animations = [];
  game.animating = false;
  var four = this.random(25);
  
  for (var i = 0; i < 25; i++) {
    var cell = new Cell(game, i);
    var value = this.random(3) + 1;
    if (i == four) 
      value = 4;
    cell.setValue(value);
    
    game.cells[i] = cell;
  }
}

Game11.prototype.random = function(to) {
  return Math.floor(Math.random() * to);
}

Game11.prototype.cellClicked = function(cell) {
  if (cell.selected) {
    this.collapse(cell);
  } else {
    this.select(cell);
  }
}

Game11.prototype.collapse = function(cell) {
  var newValue = cell.value + 1;
  if (newValue > this.highestValue) {
    this.highestValue = newValue;
  }
  
  cell.setValue(newValue);
  for (var i = 24; i >= 0; i--) {
    if (this.cells[i].selected) {
      if (i !== cell.index) {
        this.cells[i].setValue(null);
      }
      this.cells[i].select(false);
    }
  }
  for (var i = 24; i >= 0; i--) {
    if (this.cells[i].value == null) {
      this.cells[i].collapse();
    }
  }
  
  this.animate();
}

Game11.prototype.select = function(cell) {
  for (var i = 0; i < 25; i++) {
    this.cells[i].select(false);
  }
  var selectCount = 0;
  var stack = [];
  stack.push(cell);
  while (stack.length > 0) {
    var c = stack.pop();
    c.select(true);
    selectCount++;
    var ac = this.getAdjacentCells(c);
    for (var i = 0; i < ac.length; i++) {
      if (ac[i].selected == false && ac[i].value == cell.value) {
        stack.push(ac[i]);
      }
    }
  }
  if (selectCount == 1)
    cell.select(false);
}

Game11.prototype.getAdjacentCells = function(cell) {
  var result = [];
  if (cell.x > 0) result.push(this.cells[cell.index - 1]);
  if (cell.x < 4) result.push(this.cells[cell.index + 1]);
  if (cell.y > 0) result.push(this.cells[cell.index - 5]);
  if (cell.y < 4) result.push(this.cells[cell.index + 5]);
  return result;
}

Game11.prototype.registerAnimation = function(animation) {
  this.animations.push(animation);
}

Game11.prototype.animate = function() {
  this.animating = true;
  var maxTicks = 300;
  var start = new Date().valueOf();
  var timer = setInterval(function(){
    var tick = new Date().valueOf() - start;
    if (tick >= maxTicks) {
      tick = maxTicks;
      this.animating = false;
    }
    var percentage = 100 / maxTicks * tick;
    
    for (a = 0; a < this.animations.length; a++) {
      this.animations[a].step(percentage);
    }
    
    if (this.animating === false) {
      clearInterval(timer);
      this.animations.length = 0;
      console.log('x');
    }
  }.bind(this), 1);
}

/**
 * A single cell
 */
function Cell(game, index) {
  var cell = this;
  cell.game = game;
  cell.index = index;
  cell.selected = false;
  
  cell.element = document.createElement('div');
  cell.element.className = 'cell';

  cell.digit = document.createElement('div');
  cell.digit.className = 'digit';
  cell.element.appendChild(cell.digit);
  cell.element.addEventListener('click', cell.clicked.bind(cell));

  game.element.appendChild(cell.element);
   
  cell.x = index % 5;
  cell.y = Math.floor((index - cell.x) / 5);
  cell.element.style.left = (cell.x * 20) + '%';
  cell.element.style.top = (cell.y * 20) + '%';
}

Cell.prototype.clicked = function() {
  this.game.cellClicked(this);
}

Cell.prototype.setValue = function(value) {
  this.digit.classList.remove('digit' + this.value);
  this.value = value;
  if (value === null) {
    this.digit.innerText = '';
  } else {
    this.digit.classList.add('digit' + value);
    this.digit.innerText = value;
  }
}

Cell.prototype.select = function(selected) {
  this.element.classList.toggle('selected', selected);
  this.selected = selected;
}

Cell.prototype.collapse = function() {
  var value, y, cellHere, cellAbove;
  var n = this.y;
  var offset = 0;
  do {
    cellHere = this.game.cells[this.x + 5*n];
    y = n - offset;
    value = null;
    do {
      if (--y >= 0) {
        cellAbove = this.game.cells[this.x + 5*y];
        value = cellAbove.value;
        cellAbove.setValue(null);
        if (value !== null) {
          console.log('Value ' + value + ' for cell (' + this.x+','+n+') taken from cell ' + y);
        }
      } else {
        offset++;
        value = this.game.random(Math.max(3, this.game.highestValue - 2)) + 1;
        console.log('New value ' + value + ' for cell (' + this.x+','+n+')');
      }
    } while (value === null);
    
    cellHere.animateDrop(value, n-y);
  } while (--n >= 0)
}

Cell.prototype.animateDrop = function(value, distance) {
  this.setValue(value);
  new Animation(this.game, -distance, this.index, value);
}

/**
 * A cell animation
 */
function Animation(game, from, to, value) {
  this.toCell = game.cells[to];
  var cellBounds = this.toCell.element.getBoundingClientRect();
  var fromX = toX = cellBounds.left;
  var fromY = toY = cellBounds.top;
  if (from < 0) {
    fromY += cellBounds.height * from;
  } else {
    // To do: Moving from one cell to another needs an extra sprite.
    this.fromCell = game.cells[from];
    cellBounds = this.fromCell.element.getBoundingClientRect();
    var fromX = cellBounds.left;
    var fromY = cellBounds.top;
  }
  
  this.fromX = fromX;
  this.fromY = fromY;
  this.toX = toX;
  this.toY = toY;

  this.to = to;
  
  game.registerAnimation(this);
}

Animation.prototype.step = function(percentage) {
  var distance = this.toY - this.fromY;
  var step = (100-percentage) / 100;
  var Y = step * distance;
  this.toCell.digit.style.top = '' + (-Y) + 'px';
}


// Start the game
new Game11(document.querySelector('.game11'));
.game11,
.game11 .cell,
.game11 .cell .digit {
  box-sizing: border-box;
}
.game11 {
  width: 90vmin;
  height: 90vmin;
  box-sizing: border-box;
  position: relative;
  
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.game11 .cell {
  width: 20%;
  height: 20%;
  border: 2px solid #ffffff;
  position: absolute;
  font-size: 10vmin;
}

.game11 .cell .digit {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  border: 3px solid #666633;
  text-align: center;
  padding-top: 13%;
  font-family: Impact, Charcoal, sans-serif;
  color: #111111;
}
.game11 .cell.selected .digit {
  color: white;
}

.game11 .digit.digit1 {
  background-color: #CC66FF;
}
.game11 .digit.digit2 {
  background-color: #FFCC66;
}
.game11 .digit.digit3 {
  background-color: #3366FF;
}
.game11 .digit.digit4 {
  background-color: #99CCFF;
}
.game11 .digit.digit5 {
  background-color: #19D119;
}
.game11 .digit.digit6 {
  background-color: #009999;
}
.game11 .digit.digit7 {
  background-color: #996600;
}
.game11 .digit.digit8 {
  background-color: #009933;
}
.game11 .digit.digit9 {
  background-color: #666699;
}
.game11 .digit.digit10 {
  background-color: #CC66FF;
}
.game11 .digit.digit11,
.game11 .digit.digitmax {
  background-color: #FF0066;
}
<div class="game11">
</div>

如果没有CSS解法,可以按JavaScript解。这很容易,因为所有游戏的单元格都是正方形且大小相同,所以您只需将字体大小作为游戏宽度的一个因素即可。由于这些边界,不需要复杂的库。

您需要做的就是从CSS中删除font-size并将这段代码添加到Game11的构造函数中:

  // Function that calculates font size based on width of the game itself.
  var updateFontSize = function() {
    var bounds = game.element.getBoundingClientRect(); // Game
    var size = bounds.width / 5; // Cell
    size *= 0.6; // Font a bit smaller
    game.element.style.fontSize = size + 'px';
  };
  // Attach to resize event.
  window.addEventListener('resize', updateFontSize);
  // Initial font size calculation.
  updateFontSize();

更新游戏:

/**
 * Game11 class
 */
function Game11(container) {
  var game = this;
  game.element = container;
  game.cells = [];
  game.highestValue = 4;
  game.animations = [];
  game.animating = false;
  var four = this.random(25);
  
  // Function that calculates font size based on width of the game itself.
  var updateFontSize = function() {
    var bounds = game.element.getBoundingClientRect(); // Game
    var size = bounds.width / 5; // Cell
    size *= 0.6; // Font a bit smaller
    game.element.style.fontSize = size + 'px';
  };
  // Attach to resize event.
  window.addEventListener('resize', updateFontSize);
  // Initial font size calculation.
  updateFontSize();
  
  for (var i = 0; i < 25; i++) {
    var cell = new Cell(game, i);
    var value = this.random(3) + 1;
    if (i == four) 
      value = 4;
    cell.setValue(value);
    
    game.cells[i] = cell;
  }
}

Game11.prototype.random = function(to) {
  return Math.floor(Math.random() * to);
}

Game11.prototype.cellClicked = function(cell) {
  if (cell.selected) {
    this.collapse(cell);
  } else {
    this.select(cell);
  }
}

Game11.prototype.collapse = function(cell) {
  var newValue = cell.value + 1;
  if (newValue > this.highestValue) {
    this.highestValue = newValue;
  }
  
  cell.setValue(newValue);
  for (var i = 24; i >= 0; i--) {
    if (this.cells[i].selected) {
      if (i !== cell.index) {
        this.cells[i].setValue(null);
      }
      this.cells[i].select(false);
    }
  }
  for (var i = 24; i >= 0; i--) {
    if (this.cells[i].value == null) {
      this.cells[i].collapse();
    }
  }
  
  this.animate();
}

Game11.prototype.select = function(cell) {
  for (var i = 0; i < 25; i++) {
    this.cells[i].select(false);
  }
  var selectCount = 0;
  var stack = [];
  stack.push(cell);
  while (stack.length > 0) {
    var c = stack.pop();
    c.select(true);
    selectCount++;
    var ac = this.getAdjacentCells(c);
    for (var i = 0; i < ac.length; i++) {
      if (ac[i].selected == false && ac[i].value == cell.value) {
        stack.push(ac[i]);
      }
    }
  }
  if (selectCount == 1)
    cell.select(false);
}

Game11.prototype.getAdjacentCells = function(cell) {
  var result = [];
  if (cell.x > 0) result.push(this.cells[cell.index - 1]);
  if (cell.x < 4) result.push(this.cells[cell.index + 1]);
  if (cell.y > 0) result.push(this.cells[cell.index - 5]);
  if (cell.y < 4) result.push(this.cells[cell.index + 5]);
  return result;
}

Game11.prototype.registerAnimation = function(animation) {
  this.animations.push(animation);
}

Game11.prototype.animate = function() {
  this.animating = true;
  var maxTicks = 300;
  var start = new Date().valueOf();
  var timer = setInterval(function(){
    var tick = new Date().valueOf() - start;
    if (tick >= maxTicks) {
      tick = maxTicks;
      this.animating = false;
    }
    var percentage = 100 / maxTicks * tick;
    
    for (a = 0; a < this.animations.length; a++) {
      this.animations[a].step(percentage);
    }
    
    if (this.animating === false) {
      clearInterval(timer);
      this.animations.length = 0;
      console.log('x');
    }
  }.bind(this), 1);
}

/**
 * A single cell
 */
function Cell(game, index) {
  var cell = this;
  cell.game = game;
  cell.index = index;
  cell.selected = false;
  
  cell.element = document.createElement('div');
  cell.element.className = 'cell';

  cell.digit = document.createElement('div');
  cell.digit.className = 'digit';
  cell.element.appendChild(cell.digit);
  cell.element.addEventListener('click', cell.clicked.bind(cell));

  game.element.appendChild(cell.element);
   
  cell.x = index % 5;
  cell.y = Math.floor((index - cell.x) / 5);
  cell.element.style.left = (cell.x * 20) + '%';
  cell.element.style.top = (cell.y * 20) + '%';
}

Cell.prototype.clicked = function() {
  this.game.cellClicked(this);
}

Cell.prototype.setValue = function(value) {
  this.digit.classList.remove('digit' + this.value);
  this.value = value;
  if (value === null) {
    this.digit.innerText = '';
  } else {
    this.digit.classList.add('digit' + value);
    this.digit.innerText = value;
  }
}

Cell.prototype.select = function(selected) {
  this.element.classList.toggle('selected', selected);
  this.selected = selected;
}

Cell.prototype.collapse = function() {
  var value, y, cellHere, cellAbove;
  var n = this.y;
  var offset = 0;
  do {
    cellHere = this.game.cells[this.x + 5*n];
    y = n - offset;
    value = null;
    do {
      if (--y >= 0) {
        cellAbove = this.game.cells[this.x + 5*y];
        value = cellAbove.value;
        cellAbove.setValue(null);
        if (value !== null) {
          console.log('Value ' + value + ' for cell (' + this.x+','+n+') taken from cell ' + y);
        }
      } else {
        offset++;
        value = this.game.random(Math.max(3, this.game.highestValue - 2)) + 1;
        console.log('New value ' + value + ' for cell (' + this.x+','+n+')');
      }
    } while (value === null);
    
    cellHere.animateDrop(value, n-y);
  } while (--n >= 0)
}

Cell.prototype.animateDrop = function(value, distance) {
  this.setValue(value);
  new Animation(this.game, -distance, this.index, value);
}

/**
 * A cell animation
 */
function Animation(game, from, to, value) {
  this.toCell = game.cells[to];
  var cellBounds = this.toCell.element.getBoundingClientRect();
  var fromX = toX = cellBounds.left;
  var fromY = toY = cellBounds.top;
  if (from < 0) {
    fromY += cellBounds.height * from;
  } else {
    // To do: Moving from one cell to another needs an extra sprite.
    this.fromCell = game.cells[from];
    cellBounds = this.fromCell.element.getBoundingClientRect();
    var fromX = cellBounds.left;
    var fromY = cellBounds.top;
  }
  
  this.fromX = fromX;
  this.fromY = fromY;
  this.toX = toX;
  this.toY = toY;

  this.to = to;
  
  game.registerAnimation(this);
}

Animation.prototype.step = function(percentage) {
  var distance = this.toY - this.fromY;
  var step = (100-percentage) / 100;
  var Y = step * distance;
  this.toCell.digit.style.top = '' + (-Y) + 'px';
}


// Start the game
new Game11(document.querySelector('.game11'));
.game11,
.game11 .cell,
.game11 .cell .digit {
  box-sizing: border-box;
}
.game11 {
  width: 90vw;
  height: 90vw;
  max-width: 90vh;
  max-height: 90vh;
  box-sizing: border-box;
  position: relative;
  
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.game11 .cell {
  width: 20%;
  height: 20%;
  border: 2px solid #ffffff;
  position: absolute;
}

.game11 .cell .digit {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  border: 3px solid #666633;
  text-align: center;
  padding-top: 13%;
  font-family: Impact, Charcoal, sans-serif;
  color: #111111;
}
.game11 .cell.selected .digit {
  color: white;
}

.game11 .digit.digit1 {
  background-color: #CC66FF;
}
.game11 .digit.digit2 {
  background-color: #FFCC66;
}
.game11 .digit.digit3 {
  background-color: #3366FF;
}
.game11 .digit.digit4 {
  background-color: #99CCFF;
}
.game11 .digit.digit5 {
  background-color: #19D119;
}
.game11 .digit.digit6 {
  background-color: #009999;
}
.game11 .digit.digit7 {
  background-color: #996600;
}
.game11 .digit.digit8 {
  background-color: #009933;
}
.game11 .digit.digit9 {
  background-color: #666699;
}
.game11 .digit.digit10 {
  background-color: #CC66FF;
}
.game11 .digit.digit11,
.game11 .digit.digitmax {
  background-color: #FF0066;
}
<div class="game11">
</div>

不过,如果可以通过 CSS 完成,那就太好了。

您可以将字体大小设置为 vmin 值。

.game11 .cell {
  font-size: 10vmin;
}

http://jsfiddle.net/a21s77c8/