配列を回転する

条件

  • 参照元の配列を破壊しないこと。
  • 1行で済ませること。
  • 身勝手ではあるがなるべく reverse は使わないこと。
  • なるべく速度を優先すること。(調べていないけど)
const ary = [
  [1,2,3],
  [4,5,6],
  [7,8,9],
  [10,11,12]
];

const tb = a=> a.map (b=>b.join (', ')).join ('\n');


//転置(XとYを交換)
const transpose=a=>a[0].map((_,i)=>a.map(b=>b[i]));

//回転(時計回り)
//const rotate=Right a=> a[0].map((_,i)=>a.map(b=>b[i]).reverse());
const rotateRight = a=> a[0].map((_,i)=>a.reduceRight((b,c)=>[...b,c[i]],[]));

//回転(180度)
//const inversion =a=> [...a].reverse().map(b=>[...b].reverse());
//const inversion =a=> a.slice().reverse().map(b=>b.slice().reverse());
//const inversion =a=> a.reduceRight((b,c)=>[...b,[...c].reverse()],[]);
const inversion = a=>a.reduceRight((b,c)=>[...b,c.reduceRight((d,e)=>[...d,e],[])],[]);

//回転(反時計回り)
const rotateLeft = a=> a[0].reduceRight((b,_,i)=>[...b,a.map(c=>c[i])],[]);



//_____________
let A = ary;
console.log (tb (A));
console.log (tb (rotateRight (rotateRight (A))));
console.log (tb (inversion (A)));
console.log (tb (rotateLeft (rotateLeft (A))));

/*
[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [10, 11, 12],
]

[
  [12, 11, 10],
  [9, 8, 7],
  [6, 5, 4],
  [3, 2, 1],
]

*/



連続したイベントをキャンセルし、一定時間が過ぎたらイベントを発火させる

寺尾で質問をした。

イベントを遅らせて発火させ、イベントオブジェクトから target 要素を取得したい
もうちょっと自身で精査すればよかったのに・・・自己解決した。
これで文字を入力するたびに処理しなくてもよくなった。

<!DOCTYPE html>
<meta charset="UTF-8">
<title>イベントを遅らせられた</title>

<body>
<input type="text" name="inp"><br />
<input type="text" name="inp"><br />


<script>

/*****************************
  連続するイベントをキャンセルし、一定時間が経過したらイベントを発生させる

  const obj = EventDelay.create (eventType [, target[, delayTime[, useCapture]]]);

******************************/


class EventDelay {

  constructor (type, target, delayTime) {
    this.type      = type;
    this.target    = target;
    this.delayTime = delayTime;

    this.timerId   = null;
    this.event     = null;
    this.disabled  = false;
  }


  dispatch () {
    this.timerOff ();
//× this.target.dispatchEvent (this.event);
    
    if (! this.disabled)
      this.event.target.dispatchEvent (this.event);//イベントのターゲットが肝だった
  }


  timerOff () {
    if (this.timerId)
      clearTimeout (this.timerId);

    this.timerId = null;
  }


  timerOn () {
    if (this.disabled) return;

    let cbFunc = this.dispatch.bind (this);
    this.timerId = setTimeout (cbFunc, this.delayTime);
  }


  handleEvent (event) {
    this.timerOff ();

    if (this.disabled) return;

    if (event.isTrusted) {
      this.event = event;
      event.stopImmediatePropagation ();
      this.timerOn ();
    }
  }


  static create (type, target = document, delayTime = 333, useCapture = false) {
    if (1 > arguments.length || ! type)
      throw new Error ('イベントタイプが不正です');
    
    let obj = new this (type, target, delayTime);
    target.addEventListener (type, obj, useCapture);
    target = null;

    return obj;
  }

}

//-----------------------------------------------

//document に対する inputイベントを 333m/s 遅らせる
EventDelay.create ('input');

//-----------------------------------------------

document.addEventListener ('input', event=> {
  let e = event.target;
  console.log (event, e.value);// value値を取得
}, false);

</script> 

ちょっとした時間があり、全面的に見直した。

<!DOCTYPE html>
<title></title>
<meta charset="utf-8">
<style>
</style>
<body>

<script>

  class DelayTask {

    #id = null; //setTimeout関数の戻り値に使用
 
    constructor (callBackFunc, delayTime) {
      this.callBackFunc = callBackFunc; // func.bind (this)
      this.delayTime = delayTime;
    }

    execute (...args) {
      this.timer = null;
      this.callBackFunc (...args);
    }


    start (...args) {
      let cbFunc = this.execute.bind (this, ...args);
      this.timer = setTimeout (cbFunc, this.delayTime);
      return this;
    }

    stop () {
      this.timer = null;
      return this;
    }

    set timer (id) {
      if (this.#id)
        clearTimeout (this.#id);
      this.#id = id;
    }

    get timer () {
      return this.#id;
    }

    //__________________

    static create (cbFunc, delayTime = 500) {
      if ('function' != typeof cbFunc)
        throw new Error ('関数ではありません');
      return new this (cbFunc, delayTime);
    }
  }

  //_____________________________________________________

  //遅延させてハンドラを呼び出す
  class DelayHandler extends DelayTask {

    constructor (handler, delayTime) {
      super (handler, delayTime);
    }

    handleEvent (event) {
      this.stop ().start (event);
    }

    static create (handler, delayTime = 300) {
      let cbFunc;

      //引数のチェック
      switch (Object.prototype.toString.call (handler)) {
      case '[object Function]' :
        cbFunc = handler;
        break;

      case '[object Object]' :
        if ('handleEvent' in handler) {
          if ('function' === typeof handler.handleEvent)
            cbFunc = handler.handleEvent.bind (handler);
        } //else Error !! 

      default:
        throw new Error ('Not handleEvent');
        break;
      }

      return new this (cbFunc, delayTime);
    }
  }

//_____________________________________________________

function handler (event) {
  console.log(event);
  alert ('クリックされた');
}

document.addEventListener ('click', DelayHandler.create (handler, 1000), false);


</script>

WEB画面に枯葉が舞う

それにしても短い休日だった。
母さん、三途の川を渡ったころだろうか。


https://oshiete.goo.ne.jp/qa/12665779.html
枯葉よ~

<!DOCTYPE html>
<meta charset="UTF-8">
<title>Test</title>

<body>
<img class="kareha" src="data:image/gif;base64,R0lGODlhLQAqAKIAAP7jRvTSMvfZO/veQuS8H+zFJ/DMLv///yH5BAEAAAcALAAAAAAtACoAAAP4eLrc/tCRGau1k97NTyZdWBWZaDaGQZ6sEqRFew7CK89CfYsAQO88HzDUEw43RYAEdGQkH59CbDioDqAkgrSQMtwCOQEks+0GzmBR6lzRltfosKB6cXvbbi4cnLMOng0fG3lmZ2F+SU8fTHVvBnF9VYlJgh2OkHOSk0lXIpeGkX+bgCGffJmioz0mpoeaqkqeUoWnqLCxpbO0rq+bJoR7coiTnSLAdwrCfsvFIW4PoLzLYkdotXLYR3uY2GxAXdvW4gFAjrTiXTt5uuDtXVMtZFvm4PM3E1nr8/tSjPGL8vZVAgKw4KImBhM2UZAQ4MIFDUs8DGSQRQIAOw==">
<img class="kareha" src="data:image/gif;base64,R0lGODlhMAAnAKIAAOlcNOM+E8cqB9RxW96dkfDUz8hPPP///yH5BAEAAAcALAAAAAAwACcAAAP/eLrc/qS8Sas9Q97NndFdeBGfaFbDcK5OYahsfBBCKZ/FEMC3qQeGnskVAOJixGLQRMisfkUBKJQiDA3F6DREinCzAOCWa3iNH8lAWGBlkcqGNoWwVgcEPNzgdR/IHQNrAGt/JySDa0sNBWF3dmwyOQYAgQI1W3RgSj0kRY19DJkCdkYhZzmNg1GKqqONUhUFsgRvhQpNqa1Lra15BbQpNcI1Xmgpqqp3Cq2PGr9YltGWcSOBuWsYmjUKOpaOd5Z5Fri5ygSOjTNR3+CQXIFgYX7fAgpq0VmjeIbMqt1hAM5lGRhNXAdUn/oxIxjtzMFf11wN9NTOBgtZED2lGkUQYJwtEb+OWaM4EdwLJxfJIUJU0pOfG88mrZwJMB8QlHoM1AAgc6UnOjQYGuTQxA+wPWV2KJEA4MArjtuGbJnlJwfKpgd0SrP0UciCAApCbnXn1QHWBVQHcC37AOwcRRUSAAA7">

<script>

const {sin, cos, random, PI} = Math, DEG = PI / 180;

//_________

class P2 {
  constructor (x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

//_________

class Status {
  constructor (...args) {
    this.reset (...args);
  }

  reset (fall = 0, swing = 0, spin = 0) {
    this.fall = fall;
    this.swing = swing;
    this.spin = spin;
    return this;
  }

  randomReset (max, min) {
    for (let name of Object.keys (max))
      this[name] = rand (max[name], min[name]);
    return this;
  }
}

//_________

class Kareha {

  constructor (terms, target, position, character, spin, life) {
    this.terms = terms;
    this.target = target;
    this.position = position;
    this.character = character;
    this.life = life;
    this.spin = spin;
  }


  reset () {
  let t = this.terms;
    this.position = new P2 (rand (t.endArea.x, t.beginArea.x), 0);
    this.character.randomReset (t.maxStatus, t.minStatus);
    this.life = this.spin = 0;
   
    return this.disp ();
  }


  move () {
    if (null === this.life)
      this.reset ();

    let {x, y} = this.position;
    if (this.terms.endArea.y < y)
      return this.reset ();

    this.life++;
    this.spin += this.character.spin;
    y += this.character.fall + cos (this.spin * DEG);
    x += sin (this.spin * DEG) * this.character.swing;

    this.position.x = x;
    this.position.y = y;

    return this.disp ();
  }


  disp () {
    let
      style = this.target.style,
      {x, y} = this.position;
    
    if (null === this.life)
      style.opacity = 0 + '';
    else {
      let n = this.spin |0;
      style.opacity = 1 + '';
      style.left = (x |0)+ 'px';
      style.top = (y |0)+ 'px';
      style.transform = `rotate(${this.spin}deg)`;
    }
    return this;
  }


  static create (terms, target, position = new P2, character = new Status, spin = 0, life = null) {
    let obj = new this (terms, target, position, character, spin, life);
    target.style.position = 'absolute';
    return obj.reset ();
  }
 
}


function rand (max = 100, min = 0) {
  let sa = max - min;
  return random () * sa + min;
}


//_________


{
  let img = document.querySelectorAll ('img.kareha');
  
  for (let i = 0; i < 20; i++) {
    img[0].parentNode.appendChild (img[0].cloneNode (false));
    img[1].parentNode.appendChild (img[1].cloneNode (false));
    
  }
}


const
  TERMS = {
    minStatus: new Status (1, .5, .1),
    maxStatus: new Status (3, 3, 2),
    beginArea: new P2 (0, 0),
    endArea: new P2 (640, 800)
  },

  KAREHA = [...document.querySelectorAll ('img.kareha')].map (e=> Kareha.create (TERMS, e));


(function loop () {
  KAREHA.forEach (obj=> obj.move ());
  window.requestAnimationFrame (loop);
}) ();

</script>


JavaScript エンターキーで移動する。その4

teratail で回答したものより更に加筆
https://teratail.com/questions/325107

  • radio の checked は1つだけなので、最後に移動する。
  • textarea 内の改行はオートインデントが可能。
  • [Shift]+[Enter]は逆順に、[Ctrl]+[Enter]で checked の反転と textarea 内での改行
  • datalist 内の summary に対応
  • アンカータグの移動に対応
<!DOCTYPE html><title></title><meta charset="utf-8">
<style>
  a:focus, datalist:focus, input:focus {
    background: rgba(255,0,0,.2);
  }
</style>

<body>
<nav>
  <p>最初に[Tab]でフォーカスを移動した場合テストできる<br>
  <a href="#C">ABC</a>
  <a href="#C">DEF</a>
  <a href="#A">GHI</a><br>
</nav>

<form>
  <p>フォーム内のアンカータグを移動する<br>
    <a href="#C">ABC</a>
    <a href="#C">DEF</a>
    <a href="#A">GHI</a>

  <p>通常の INPUT要素<br>
    1.<input name="d0" value="">
    2.<input name="d1" value="">
  
  <p>表示されていない要素<br>
    1.<input type="hidden" name="d2">
    2.<input type="hidden" name="d2" style="display:none;">
    3.<input type="hidden" name="d2" style="visibility:hidden;">
    4.<input type="hidden" name="d2" style="opacity: 0;">
  
  <p>INPUT[type=radio]要素の場合<br>
    <label>1.<input type="radio" name="d3" value="1">abc</label>
    <label>2.<input type="radio" name="d3" value="2">def</label>
    <label>3.<input type="radio" name="d3" value="3">ghi</label>
  
  <p>INPUT[type=checkbox]要素の場合<br>
    <label><input type="checkbox" name="d4" value="1">abc</label>
    <label><input type="checkbox" name="d4" value="2">def</label>
    <label><input type="checkbox" name="d4" value="3">ghi</label><br>

  <p>SELECT要素<br>
    <select name="d5"><option value="">-- <option value="1">abc <option value="2">def</select>

  <p>TEXTAREA要素<br>
    <textarea name="d6" cols="80" rows="5">
  改行すると、その行の最初のインデントが継続されます
    改行せずに移動する場合は[Ctrl]+[Enter]、設定で逆の動作をします
      改行すると、その行の最初のインデントが継続されます
    </textarea>

  <p>DATALIST要素にSUMMARYがある場合<br>
    <details>
      <summary>Summary OPEN1</summary>
      <ol><li>abc <li>def <li>ghi</ol>
    </details>
    <details open>
      <summary>Summary OPEN2</summary>
      <ol><li>abc <li>def <li>ghi</ol>
    </details>
  
  <p>INPUT[type=button] / BUTTON 要素の場合<br>
    <input type="button" value="フォームの移動を無効にする" onclick="F.disabled=true">
    <input type="button" value="フォームの移動を有効にする" onclick="F.disabled=false">
  </p>
      
</form>
<script>

/*
  [Enter] 要素を次に移動する
  [Shift]+[Enter] 前に移動する
  [Ctrl]+[Enter]: 作用する
*/



//エンターキーで項目を移動する
//
// input 要素が1つの場合、機能しない。
// http://www.w3.org/TR/html5/constraints.html#implicit-submission

class ExEnter {

  #root    = null;//エンターキーで巡る親要素
  #walker  = null;//フォーカス要素の移動は treeWalker を利用する
  #imeFlag = null;//IME日本語入力中で未変換文字があり Enterが押されたことを感知するにはkeypress イベントがが実行されないことを利用する

  //________________________________________

  constructor (root = document.documentElement, option = { }, disabled = false) {
    this.root = root;//setter 側で初期化処理を行う
    this.option = Object.assign ({ }, this.constructor.defaultOption, option);
    this.disabled = disabled; //true:機能停止
  }

  // rootが指定されたら初期化する
  set root (root) {
    let handler = this.constructor.#handler;
    //まずイベントを取り外す
    if (this.#root)
      Object.keys (handler).forEach (eventType=>
        this.#root.removeEventListener (eventType, this, false)
      );

    //イベントを登録する
    Object.keys (handler).forEach (eType=>
      root.addEventListener (eType, this, false)
    );
    
    this.#root = root;
    this.#walker = root.ownerDocument.createTreeWalker (
      root,
      NodeFilter.SHOW_ELEMENT, //要素ノードのみ
      this.constructor.#nodeFilter.bind (this)
    );
  }


  get root () {
    return this.#root;
  }


  //ルートの中の対象となる最初の要素を返す
  get firstElement () {
    this.#walker.currentNode = this.root;
    return this.#walker.firstChild ();
  }


  //ルートの中の対象となる最後の要素を返す
  get lastElement () {
    this.#walker.currentNode = this.root;
    return this.#walker.lastChild ();
  }


  //ルートの中の対象となる要素のフォーカスを移動する direction= true:戻る /false:次へ
  move (direction = false, target = this.root.ownerDocument.activeElement) {
    if (this.disabled)
      return;
    if (! this.root.contains (target))
      throw new Error ('ルート要素に含まれていない要素が指定されました');

    const
      walker = this.#walker,
      isLoop = this.option.loop;

    walker.currentNode = target;
    let e = (direction)
      ? walker.previousNode () || (isLoop ? this.lastElement: null)
      : walker.nextNode ()     || (isLoop ? this.firstElement: null);

    if (e) e.focus ();
  }


  //対象となる要素にそこでの挙動を委ねる。戻り値で項目移動の判別
  entrust (e, sw, prev) { //sw == ctrl, prev == shift
    if (this.disabled)
      return;

    let stay = false, tag = e.tagName; //stay == true 留まることを意味する

    if ('TEXTAREA' === tag && !prev) { //shiftキーが押されていない事が条件
      if (sw ^ this.option.textarea) {
        //改行が行われる場合は留まる
        this.constructor.#insertCRLF (e, this.option.autoIndent);
        stay = true;
      }
    }
    else if (sw) {
      switch (tag) {
      case 'SUMMARY' :
        let parent = e.parentNode;
        parent.hasAttribute ('open')
          ? parent.removeAttribute ('open')
          : parent.setAttribute ('open', '');
        stay = true;
        break;

      case 'A' :
        this.constructor.#fireEvent (e);
        stay = true;
        break;

      case 'INPUT' :
        switch (e.type) {
        case 'radio':
          if (e.checked = !e.checked) {//radio の checkedは選択肢が1つなので群の最後に移動
            let radios = this.root.querySelectorAll (`input[type="radio"][name="${e.name}"]`);
            this.move (false, radios[radios.length-1]);
          }
          break;
        case 'checkbox' :
          e.checked = !e.checked;
          stay = true;
          break;
          
        case 'file' : case 'color' :
        case 'button' : case 'submit' : case 'reset' :
          this.constructor.#fireEvent (e);
          stay = true;
          break;
        }
        break;
      }
    }


    //値が有効? / summary & anchor has not checkValidity
    if (! stay && this.option.invalidStop && e.checkValidity) //
      stay = ! e.checkValidity ();

    return stay;
  }


  //イベントハンドラ(各typeによって分岐)
  handleEvent (event) {
    let handler = this.constructor.#handler[event.type];
    if (handler)
      handler.call (this, event);
  }

  //________________________________________


  //記載されたイベントは定義時に登録される {keyDown, keyup}
  static #handler = {
    'keydown':
      function (event) {
        if ('Enter' === event.code) {
          let { target: { nodeName, type } } = event;
          if (/^(submit|reset|button|textarea|file|color)$/.test (type) || /^(SUMMARY|A)$/.test (nodeName))
            event.preventDefault ();
          this.#imeFlag = event.isComposing;//IME変換中にEnterキーで確定、keyupでスルーするため
        } else
          this.#imeFlag = false;
      },

    'keyup':
      function (event) {
        if ('Enter' === event.code) {
          //IMEの動作で未変換中の文字があると判断してスルー
          if (this.#imeFlag)
            return event.preventDefault ();
          let { shiftKey, ctrlKey, target } = event, tmp;
          if (! (tmp = this.entrust (target, ctrlKey, shiftKey)))
            this.move (shiftKey, target);
          this.#imeFlag = false;
        }
      }
  };


  //TreeWalker用のフィルター関数(要.bind(this))
  static #nodeFilter (node) {
    const
      accept = NodeFilter.FILTER_ACCEPT,
      skip = NodeFilter.FILTER_SKIP,
      {option} = this;
    
    switch (node.nodeName) {
    case 'INPUT' : case 'TEXTAREA' : case 'SELECT' : case 'BUTTON' :
      if (node.disabled || node.readOnly) break;
      if (option.skip_tabIndex && ('-1' === node.getAttribute ('tabIndex'))) break;
      if (this.constructor.#isHide (node)) break;
      return accept;

    case 'A' : //href = '#...' で始まるもののみ対象とする
      if (option.anchor)
        return accept;
      break;
    
    case 'SUMMARY' :
      if (! option.details) break;
      return accept;
    }
    return skip;
  }


  //祖先の要素が隠された状態にあるか?
  static #isHide (node) {
    const chks = ['display', 'visibility', 'opacity', 'height', 'width'];

    for (let e = node; e !== document.body; e = e.parentNode) {
      let
        cs = getComputedStyle (e, null),
        [d, v, o, h, w] = chks.map (p=> cs.getPropertyValue (p));//それぞれchksの値

      if (
        'none' === d || 'hidden' === v || 0 == parseFloat (o) ||
        ! ('auto' === h || 0 < parseInt (h, 10)) ||
        ! ('auto' === w || 0 < parseInt (w, 10))
      ) return true;
    }
    return false;
  }


  //対象の要素にクリックイベントを発火させる
  static #fireEvent (target) {
    if (! target)
      throw new Error ('引数が無効!!');

    let event = target.ownerDocument.createEvent ('MouseEvents');
    event.initEvent ('click', false, true);
    target.dispatchEvent (event);
    return event;
  }


  //textarea要素に改行を挿入する
  static #insertCRLF (t, autoIndent = false, start = t.selectionStart || 0) {
    let
      str = '\n',
      value = t.value,
      first = value.slice (0, start),
      last = value.slice (start).replace (/^([\t\u3000\u0020]+)/, '');

    if (autoIndent) {
      let spc = /(?:\n|^)([\t\u3000\u0020]+).*$/.exec (first);
      if (spc) str += spc[1];
    }

    t.value = first + str + last;
    t.selectionStart = t.selectionEnd = start + str.length;
  }

  //________________________________________

  //初期設定オプション値
  static defaultOption = {
    loop:       true, //要素を巡回する
    skip_tabIndex: true, //tabIndex属性が "-1" の場合フォーカスを飛ばす
    anchor:     true, //アンカータグを有効にする(但し href属性の値が "#"から始まる場合)
    details:    true, //details要素を巡回対象とする
    invalidStop:true,//要素の値が不正な場合でも移動する

    textarea:   true, //true: 改行[Enter], 移動[CTRL]+[Enter], false: 移動[Enter], 改行[CTRL]+[Enter]
    autoIndent: true, //textarea要素内でのオートインデントを行う
  };


  //インスタンスの作成
  static create (root = document.documentElement, option = { }, disabled = false) {
    let obj = new this (root, option, disabled);
    return obj;
  }

}
 
 
const enter = ExEnter.create (TEST);